最近一直有个疑问,创建线程安全的懒汉式单例模式到底需不需要volatile,为此深究了一下。
首先先写一个不加的情况:
/**
* @author PanBangLin
* @version 1.0
* @date 2022/7/3 11:26
*/
public class SingleTest {
private static SingleTest SINGLE;
private SingleTest() {};
public SingleTest getSINGLE(){
// double check lock 双重检查锁
if(SINGLE == null){ // 第一次判断
synchronized (SingleTest.class){
if (SINGLE == null){ // 第二次判断
SINGLE = new SingleTest();
}
}
}
return SINGLE;
}
}
为什么要使用双重检测锁呢?因为在并发的情况下,有可能有多个线程同时符合了第一次判断的情况,进入到了synchronized处等待,等到第一个线程实例化好对象后,将锁释放,后面卡在锁上的线程又会陆续实例化对象,造成重复实例化。
继续回到 volatile 的问题,了解这个问题,需要先明白对象实例化的过程:
op1:分配内存空间
op2:初始化对象
op3:将对象的引用,指向分配的内存
如果发生指令重排
op1:分配内存空间
op2:将对象的引用,指向分配的内存
op3:初始化对象
那么如果在第一个线程实例化的过程中,后续再有线程进入到了第一次判断不等于null的部分,那么会直接返回这个半实例化的对象,因为这个时候SINGLE 已经不等于null了,但是这个对象是无法使用的,后面这个线程调用的时候就会发生报错。
所以我们需要把这个变量加上 volatile关键字,让其不发生指令重排,以及修改后的值马上同步到主存上去。
最终效果:
/**
* @author PanBangLin
* @version 1.0
* @date 2022/7/3 11:26
*/
public class SingleTest {
private volatile static SingleTest SINGLE;
private SingleTest() {};
public SingleTest getSINGLE(){
// double check lock 双重检查锁
if(SINGLE == null){ // 第一次判断
synchronized (SingleTest.class){
if (SINGLE == null){ // 第二次判断
SINGLE = new SingleTest();
}
}
}
return SINGLE;
}
}