设计模式 创建型模式 - 单例模式(一)

单例模式(Singleton Pattern)是Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

一、单例模式的结构

单例模式的主要有以下角色:

(1)单例类。只能创建一个实例的类

(2)访问类。使用单例类

二、单例模式的实现

单例设计模式主要分为两类:

(1)饿汉式:类加载就会导致该单实例对象被创建

(2)懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

1. 饿汉式-方式1(静态成员变量方式)

/**
 * 饿汉式
 *      静态变量创建类的对象
 */
public class Singleton {

    // 1.私有构造方法
    private Singleton() {}

    // 2.在成员位置创建该类的对象
    private static Singleton instance = new Singleton();

    // 3.提供静态方法获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}

说明:

该方式在成员位置声明 Singleton 类型的静态变量,并创建Singleton 类的对象 instance。instance 对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

2. 饿汉式-方式2(静态代码块方式)

/**
 * 饿汉式
 * 在静态代码块中创建该类对象
 */
public class Singleton {

    // 1.私有构造方法
    private Singleton() {
    }

    // 2.在成员位置创建该类的对象
    private static Singleton instance;

    // 3.静态代码块中赋值
    static {
        instance = new Singleton();
    }

    // 4.提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

说明:

该方式在成员位置声 Singleton 类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本一样,当然该方式也存在内存浪费问题。

3. 懒汉式-方式1(线程不安全)

/**
 * 懒汉式
 * 线程不安全
 */
public class Singleton {

    private Singleton() {
    }

    private static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:

        从上面的代码我们可以看出该方式在成员位置声明 Singleton 类型的静态变量,并没有进行对象的赋值操作。但调用 getInstance() 方法获取 Singleton 类的对象的时候才创建 Singleton 类的对象,这样就实现了懒加载的效果。但是,多线程环境会出现线程安全问题。

4. 懒汉式-方式2(线程安全)

/**
 * 懒汉式
 * 线程安全
 */
public class Singleton1 {

    private Singleton1() {
    }

    private static Singleton1 instance;

    public synchronized static Singleton1 getInstance() {
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}

说明:

        该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在 getInstance() 方法上添加了 synchronized 关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化 instance 的时候才出现线程安全问题,一旦初始化完成就不存在了。

5. 懒汉式-方式3(双重检查锁方式)

        再来讨论一下懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。

/**
 * 懒汉式
 * 双重检测锁
 */
public class Singleton {

    private Singleton() {
    }

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

说明:

        双重检测锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题。

        在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

        只需要使用 volatile 关键字,可以保证可见性和有序性。

小结:

        添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

6. 懒汉式-方式4(静态内部类方式)

        静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有在内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

/**
 * 懒汉式
 * 静态内部类
 */
public class Singleton {

    private Singleton() {
    }

    // 定义一个静态内部类
    private static class SingletonHolder {
        // 在内部类中声明并初始化外部类的一个对象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

说明:

        静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

7. 枚举方式

        枚举方式实例单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次。而且枚举类型是所有单例实现中唯一一种不会被破坏的单例实现模式。

设计模式 创建型模式 - 单例模式(一)_第1张图片

说明:

        枚举方式属于饿汉式方式。

三、单例存在的问题

1.问题演示

破坏单例模式:

        使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化反射

1.1 序列化反序列化

设计模式 创建型模式 - 单例模式(一)_第2张图片

1.2 反射

设计模式 创建型模式 - 单例模式(一)_第3张图片

2. 问题的解决

2.1 序列化、反序列化破坏单例模式的解决办法

        在 Singleton 类中添加 readResolve() 方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。

设计模式 创建型模式 - 单例模式(一)_第4张图片

 源码解析

设计模式 创建型模式 - 单例模式(一)_第5张图片

设计模式 创建型模式 - 单例模式(一)_第6张图片

2.2 反射方式破解单例的解决方法

设计模式 创建型模式 - 单例模式(一)_第7张图片

说明:

        当通过反射方式调用构造方法进行创建时,直接抛异常。不运行此中操作。

四、JDK 源码解析-Runtime类

1. 单例模式

设计模式 创建型模式 - 单例模式(一)_第8张图片

从源代码中可以看出 Runtime 类使用的是饿汉式(静态属性)方式来实现单例模式的。

2. 使用Runtime 类中的方法

设计模式 创建型模式 - 单例模式(一)_第9张图片

你可能感兴趣的:(黑马【设计模式】,设计模式,java)