一 错误的用双重检查锁定
public class DoubleCheckedLocking { // 1
private static Instance instance; // 2
public static Instance getInstance() { // 3
if (instance == null) { // 4:第一次检查
synchronized (DoubleCheckedLocking.class) { // 5:加锁
if (instance == null) // 6:第二次检查
instance = new Instance(); // 7:问题的根源出在这里
} // 8
} // 9
return instance; // 10
} // 11
}
错误原因:第7行创建对象,可以分解为三步,分别为分配内存、初始化对象、引用指向内存地址,后两步可能存在重排序,导致访问到未初始化的成员
解决方法:1)不允许重排序。2)允许重排序,但不允许其他线程“看到”这个重排序。
二 基于volatile的解决方案,禁止重排序
用volatile修饰instance。
三 基于类初始化的解决方案
JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在
执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance ; // 这里将导致InstanceHolder类被初始化
}
}
四 综述
字段延迟初始化降低了初始化类或创建实例的开销,但增加了访问被延迟初始化的字段
的开销。在大多数时候,正常的初始化要优于延迟初始化。如果确实需要对实例字段使用线程
安全的延迟初始化,请使用上面介绍的基于volatile的延迟初始化的方案;如果确实需要对静
态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。