单例模式-双重校验锁

参考;https://blog.csdn.net/weixin_44471490/article/details/108929289

双重校验锁

饿汉模式是不需要加锁来保证单例的,而懒汉模式虽然节省了内存,但是却需要使用锁来保证单例,因此,双重校验锁就是懒汉模式的升级版本。

普通懒汉式

public class Singleton {
    
  	private static Singleton INSTANCE;
  
  	private Singleton() {}
  	
  	public static Singleton getInstance() {
      	if (INSTANCE == null) {
          	INSTANCE = new Singleton();
        }
      	return INSTANCE;
    }
}

单线程懒汉模式的问题

上面这段代码在单线程环境下没有问题,但是在多线程的情况下会产生线程安全问题。
在多个线程同时调用getInstance方法时,由于方法没有加锁,可能会出现以下情况
① 这些线程可能会创建多个对象
② 某个线程可能会得到一个未完全初始化的对象
对于 ① 的情况解释如下:

public static Singleton getInstance() {
    if (INSTANCE == null) {
        /**
         * 由于没有加锁,当线程A刚执行完if判断INSTANCE为null后还没来得及执行INSTANCE = new Singleton()
         * 此时线程B进来,if判断后INSTANCE为null,且执行完INSTANCE = new Singleton()
         * 然后,线程A接着执行,由于之前if判断INSTANCE为null,于是执行INSTANCE = new Singleton()重复创建了对象
         */
        INSTANCE = new Singleton();
    }
    return INSTANCE;
}

对于 ② 的情况解释如下:

public static Singleton getInstance() {
    if (INSTANCE == null) {
        /**
         * 由于没有加锁,当线程A刚执行完if判断INSTANCE为null后开始执行 INSTANCE = new Singleton()
         * 但是注意,new Singleton()这个操作在JVM层面不是一个原子操作
         *
         *(具体由三步组成:1.为INSTANCE分配内存空间;2.初始化INSTANCE;3.将INSTANCE指向分配的内存空间,
         * 且这三步在JVM层面有可能发生指令重排,导致实际执行顺序可能为1-3-2)
         *
         * 因为new操作不是原子化操作,因此,可能会出现线程A执行new Singleton()时发生指令重排的情况,
         * 导致实际执行顺序变为1-3-2,当执行完1-3还没来及执行2时(虽然还没执行2,但是对象的引用已经有了,
         * 只不过引用的是一个还没初始化的对象),此时线程B进来进行if判断后INSTANCE不为null,
         * 然后直接把线程A new到一半的对象返回了
         */
        INSTANCE = new Singleton();
    }
    return INSTANCE;
}

双重校验锁模式

public class Lock2Singleton {
  	private volatile static Lock2Singleton INSTANCE;    // 加 volatile
  
  	private Lock2Singleton() {}
  
  	public static Lock2Singleton getSingleton() {
      	if (INSTANCE == null) {                         // 双重校验:第一次校验
          	synchronized(Lock2Singleton.class) {        // 加 synchronized
              	if (INSTANCE == null) {                 // 双重校验:第二次校验
                  	INSTANCE = new Lock2Singleton();
                }
            }
        }
      	return INSTANCE;
    }
}

过程如下:
判断 INSTANCE 是否为null,检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量;

  • 不为null,直接返回,不用去竞争锁
  • 为null,获取锁,然后再次判断(虽然已经判断过,但是在第一个if和synchronized之间仍有可能被另外线程插入导致第一个if判断为null时,当进入同步代码块之后再次判断时已经不为null了,所以需要再次判断)
    • 是否为null
    • 为null,创建并返回
    • 不为null,直接返回
posted @ 2023-10-27 15:20  hasome  阅读(37)  评论(0编辑  收藏  举报