单例模式-双重检查加锁为什么需要加上volatile关键字?

public class DoubleCheckedLocking {
    
    
	private static DoubleCheckedLocking instance;
	public static DoubleCheckedLocking getInstance(){
    
    
		if(instance == null){
    
    // 4.第一次检查
			synchronized (DoubleCheckedLocking.class){
    
     // 5.加锁
				if(instance == null){
    
     // 6. 第二次检查
				instance = new DoubleCheckedLocking(); // 7.问题的根源处在这里
			}
		}
		}
	return instance;
	}
}

上面代码有一个问题:

在线程执行到第4行,代码读取到 instance 不为 null 时,instance引用的对象有可能还没有完全初始化。

问题的根源:

前面的 双重检查锁定上述代码的第7行(instance = new DoubleCheckedLocking())创建了一个对象。这一行代码可以分解为 下面的3行代码。

memory = alloct();  //:1:分配对象的内存空间
ctorinstance(memory);  //2: 初始化对象
instace = memory;  //3:设置instace指向刚分配的内存地址

上面的3行伪代码中的2和3之间,可能被重排序,重排序如下:

memory = alloct();  //:1:分配对象的内存空间
instace = memory; //3:设置instace指向刚分配的内存地址
                  // 主要此对象还没有被初始化
ctorinstance(memory);  //2: 初始化对象

多线程执行结果如下:

时间 线程A 线程B
t1 A1: 分配对象的内存空间
t2 A3: 设置instance 执行内存空间
t3 B1: 判断instance 是否为空
t4 B2: 由于instance不为null, 线程B 将访问instance 引用的对象
t5 A2: 初始化对象
t6 A4: 访问instance 引用的对象

解决方案: 加上volatile关键字

public class DoubleCheckedLocking {
    
    
    private static volatile DoubleCheckedLocking instance;
    public static DoubleCheckedLocking getInstance(){
    
    
        if(instance == null){
    
    // 4.第一次检查
            synchronized (DoubleCheckedLocking.class){
    
     // 5.加锁
                if(instance == null){
    
     // 6. 第二次检查
                    instance = new DoubleCheckedLocking(); // 7.问题的根源处在这里
                }
            }
        }
        return instance;
    }

史上最全的并发编程脑图:https://www.processon.com/view/5b1f1ad7e4b03f9d251c06e5#map

猜你喜欢

转载自blog.csdn.net/fd2025/article/details/108359755