延迟初始化对象的错误用法:双重检查锁定
双重检查锁定的设想:
- 多个线程试图在同一时间创建对象,会通过加锁来保证只有一个线程能创建对象
- 在对象创建好后,执行 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 线程会看到一个还没有被初始化的对象。
结论
如果在
instance = new Instance();
发生重排序,另一个并发线程 B 就有可能在第一次判断时不为 null,B将会访问 instance 所引用的对象,但此时这个对象可能还没有被 A 线程初始化。
没有修不好的电脑