双重检查锁定的设想:

  1. 多个线程试图在同一时间创建对象,会通过加锁来保证只有一个线程能创建对象
  2. 在对象创建好后,执行 getInstance() 方法将不需要获取锁,直接返回已创建好的对象

对于 Instance 类,以下是双重检查锁定代码

public class DoubleCheckedLocking {
    private static Instance instance;

    public static Instance getInstance() {
        // 第一次检查
        if (instance == null) {
            // 加锁
            synchronized (DoubleCheckedLocking.class) {
                // 第二次检查
                if (instance == null) {
                    // 问题的根源
                    instance = new Instance();
                }
            }
        }

        return instance;
    }
}

问题:

第一次检查时,代码读取到 instance 不为 null 时,instance 引用的对象有可能还没有完成初始化。

根源:

上面的问题根源代码行

instance = new Instance();

可以拆解为以下 3 行伪代码:

// 1.分配对象的内存空间
memory = allocate();
// 2.初始化对象
ctorInstance(memory);
// 3.设置 instance 指向刚分配的内存地址
instance = momory;

上述代码的 2 和 3 之间,可能会存在重排序。发生重排序后的执行时序如下:

// 1.分配对象的内存空间
memory = allocate();
// 3.设置 instance 指向刚分配的内存地址
instance = momory;
// 2.初始化对象
ctorInstance(memory);
单线程执行时序图

2 和 3 虽然重排序了,但并不违反规则,只要保证 2 在 4 前面就可以保证结果不变
延迟初始化对象的错误用法:双重检查锁定_计算机

多线程执行时序图

当在初始化时,B 线程会看到一个还没有被初始化的对象。
延迟初始化对象的错误用法:双重检查锁定_计算机_02

结论

如果在

instance = new Instance();

发生重排序,另一个并发线程 B 就有可能在第一次判断时不为 null,B将会访问 instance 所引用的对象,但此时这个对象可能还没有被 A 线程初始化。

没有修不好的电脑