设计模式一、创建型模式-单例模式

系列传送门

创建型模式
这一类模式用于创建对象。
隐藏或抽象创建对象的细节,而不是使用 new直接创建对象。

设计模式一、创建型模式-单例模式
设计模式二、创建型模式-简单工厂模式
设计模式三、创建型模式-工厂模式
设计模式四、创建型模式-抽象工厂模式
设计模式五、创建型模式-建造者模式

简单单例(推荐)

public class SimpleSingleton {

    // TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
    private SimpleSingleton() {
    }

    // TODO 第2步:定义并初始化私有静态实例变量,在类加载阶段即完成该静态实例的初始化
    private static SimpleSingleton instance = new SimpleSingleton();

    // TODO 第3步:对外提供一个获取私有静态实例方法
    public static SimpleSingleton getInstance() {
        return instance;
    }
}

基于类加载过程进行说明

  1. 加载
    加载SimpleSingleton.class字节码文件到JVM,生成一个SimpleSingleton的java.lang.Class对象(生成在方法区)。
  2. 验证
    验证SimpleSingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。
  3. 准备
    为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。
  4. 解析
    把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。
  5. 初始化
    执行类构造器方法,完成类变量(这里是instance)的赋值操作(这里是在堆内存上创建SimpleSingleton实例)和执行类静态块(这里没有类静态块)。
    如果没有类变量和类静态块,JVM不会生成类构造器方法。
  6. 使用
  7. 卸载

优点
线程安全。
在JVM启动时的类加载阶段即完成静态实例变量的初始化,不存在多线程争抢创建静态实例的情况。
缺点
对于大量不需要马上使用的单例对象,会在类加载阶段创建大量的实例,占用内存空间。
推荐
如果能确保这个单例一定有使用的机会,也就是说这个单例迟早都要初始化,那么用这个方式也是最简单最直接最好用的。

不安全的延迟初始化单例(不推荐)

public class UnsafeLazySingleton {

    // TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
    private UnsafeLazySingleton() {
    }

    // TODO 第2步:定义私有静态实例变量,这里不初始化
    private static UnsafeLazySingleton instance;

    // TODO 第3步:对外提供一个获取私有静态实例方法
    public static UnsafeLazySingleton getInstance() {
        // TODO 第4步:在这里初始化私有静态实例变量
        if (instance == null) {
            instance = new UnsafeLazySingleton();
        }
        return instance;
    }
}

基于类加载过程进行说明

  1. 加载
    加载UnsafeLazySingleton.class字节码文件到JVM,生成一个UnsafeLazySingleton的java.lang.Class对象(生成在方法区)。
  2. 验证
    验证UnsafeLazySingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。
  3. 准备
    为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。
  4. 解析
    把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。
  5. 初始化
    执行类构造器方法,完成类变量(这里是instance)的赋值操作(这里没有对静态变量赋值,所以不会初始化instance)和执行类静态块(这里没有类静态块)。
    如果没有类变量和类静态块,JVM不会生成类构造器方法。
  6. 使用
    线程调用getInstance()方法,发现instance为null,此时会初始化instance静态变量(这里是在堆内存上创建UnsafeLazySingleton实例)。
  7. 卸载

优点
只在第一次使用单例实例时,才初始化单例实例,节省内存空间。
缺点
线程不安全。
多线程情况下,会出现争抢初始化单例实例的情况,最坏情况是每个线程都创建了一次instance实例,造成每个线程持有的instance实例不一致。
不推荐

安全的延迟初始化单例(不推荐)

public class SafeLazySingleton {

    // TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
    private SafeLazySingleton() {
    }

    // TODO 第2步:定义私有静态实例变量,这里不初始化
    private static SafeLazySingleton instance;

    // TODO 第3步:对外提供一个获取私有静态实例方法,该接口使用synchronized关键字,保证多线程并发情况下只有一个线程能执行该方法
    public synchronized static SafeLazySingleton getInstance() {
        // TODO 第4步:在这里初始化私有静态实例变量
        if (instance == null) {
            instance = new SafeLazySingleton();
        }
        return instance;
    }
}

基于类加载过程进行说明

  1. 加载
    加载SafeLazySingleton.class字节码文件到JVM,生成一个SafeLazySingleton的java.lang.Class对象(生成在方法区)。
  2. 验证
    验证SafeLazySingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。
  3. 准备
    为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。
  4. 解析
    把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。
  5. 初始化
    执行类构造器方法,完成类变量(这里是instance)的赋值操作(这里没有对静态变量赋值,所以不会初始化instance)和执行类静态块(这里没有类静态块)。
    如果没有类变量和类静态块,JVM不会生成类构造器方法。
  6. 使用
    线程调用getInstance()方法时,先获取当前类的java.lang.Class实例的锁。
    如果拿到了锁,执行到getInstance()方法内部,发现instance为null,此时会初始化instance静态变量(这里是在堆内存上创建SafeLazySingleton实例)。
    如果没拿到锁,则阻塞等待。
  7. 卸载

优点1
只在第一次使用单例实例时,才初始化单例实例,节省内存空间。
优点2
线程安全。
多线程情况下,只有一个线程能执行getInstance()静态方法。
这里是锁getInstance()静态方法,加锁的对象是当前类的java.lang.Class实例。
缺点
并发情况下,多个线程同时调用getInstance()静态方法时会争抢锁,一次只有一个线程能获得锁,其他线程只能阻塞等待。
每次调用getInstance()静态方法时都要获得锁,执行效率太低
不推荐

双重检测的安全的延迟初始化单例(不怎么推荐)

public class DoubleCheckSafeLazySingleton {

    // TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
    private DoubleCheckSafeLazySingleton() {
    }

    // TODO 第2步:定义私有静态实例变量,这里不初始化
    private static DoubleCheckSafeLazySingleton instance;

    // TODO 第3步:对外提供一个获取私有静态实例方法
    public static DoubleCheckSafeLazySingleton getInstance() {
        // TODO 第4步:在这里检测私有静态实例变量是否已被初始化
        if (instance == null) {
            // TODO 第5步:还未被初始化,则先给当前类的java.lang.Class实例加锁
            synchronized (DoubleCheckSafeLazySingleton.class) {
                // TODO 第6步:获得锁之后,在这里再次检测私有静态实例变量是否已被初始化
                if (instance == null) {
                    // TODO 第7步:还未被初始化,则在这里初始化私有静态实例变量
                    instance = new DoubleCheckSafeLazySingleton();
                }
            }
        }
        return instance;
    }
}

基于类加载过程进行说明

  1. 加载
    加载DoubleCheckSafeLazySingleton.class字节码文件到JVM,生成一个DoubleCheckSafeLazySingleton的java.lang.Class对象(生成在方法区)。
  2. 验证
    验证DoubleCheckSafeLazySingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。
  3. 准备
    为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。
  4. 解析
    把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。
  5. 初始化
    执行类构造器方法,完成类变量(这里是instance)的赋值操作(这里没有对静态变量赋值,所以不会初始化instance)和执行类静态块(这里没有类静态块)。
    如果没有类变量和类静态块,JVM不会生成类构造器方法。
  6. 使用
    当前线程调用getInstance()方法,先判断instance是否已被初始化。
    如果instance已被初始化,则无需获得锁,直接返回instance
    如果instance还未被初始化,则先获得当前类的java.lang.Class实例的锁。
    如果没有拿到锁,则当前线程阻塞等待。
    当前线程拿到锁之后,执行到getInstance()方法内部。
    发现instance已被初始化,直接跳出同步块。
    发现instance为null,此时会初始化instance静态变量(这里是在堆内存上创建DoubleCheckSafeLazySingleton实例)。
  7. 卸载

优点1
只在第一次使用单例实例时,才初始化单例实例,节省内存空间。
优点2
线程安全。
多线程情况下,多线程情况下,只有一个线程能获得当前类的java.lang.Class实例的锁并检测和执行初始化单例实例操作。
这里是锁getInstance()静态方法内部的代码块,加锁的对象是当前类的java.lang.Class实例。
当私有静态实例已被初始化时,所有线程将不必去获得锁。
只有当私有静态实例未被初始化时,才会去获得锁,极大的提高了执行效率。
既保证了单例实例的延迟初始化,又兼顾了线程安全。
缺点
这个方式的写法比较繁琐,如果这个单例迟早都要使用,还不如使用SimpleSingleton的方式。
不怎么推荐

你可能感兴趣的:(设计模式一、创建型模式-单例模式)