在单例的实现模式中,很多大师都说过双重检查对JAVA不适用,比如说《设计模式》的作者闫宏,原因都是说JAVA语言构造器问题,具体说来大概都是以下两种:
1. JAVA对象的构造过程不是原子操作;
2. JAVA对象的构造化为本地代码时,有可能发生指令重排序现象;
我们先看双重检查的单例代码实现:
public class DoubleCheck {
private static DoubleCheck instance;
private DoubleCheck() {
}
public static DoubleCheck getInstance() {
if(instance == null) {
synchronized (DoubleCheck.class) {
// 双重检查
if(instance == null) {
// 构造器的赋值操作不是原子,有可能出现先赋值后初始化的现象
instance = new DoubleCheck();
}
}
}
return instance;
}
}
首先说明一点,构造器问题导致的单例失败现象是很难复现的,即使上述的单例双重检查失败了,请记住,也只会出现未构造完成的对象,绝不会出现构造器被调用两次的情况。
双重检查失败的原因在于,构造器的赋值操作会分解为很多步骤,有可能刚赋值(分配内存与默认值),还没来得及执行初始化方法,CPU就已经切换到另一个线程,而这个线程刚好又在执行第一次检查,从而返回了一个未初始化完成的对象。
所以,双重检查的问题也就变成了对象如何安全发布的问题?而安全发布一个对象有四种办法:
1. 直接对静态属性进行初始化(懒汉模式);
2. 用volatile或AtomicReference发布对象(双重检查成立);
3. 用诸如synchronizedMap、synchronizedList、ConcurrentMap的安全容器发布对象;
4. 在同步块中发布对象(静态方法加锁模式);
综上所述,只要能安全地发布对象,那么单例模式就是安全的,所以单例模式的实现也就至少有四种实现方法。
结论
双重检查归根到底还是在于对象的安全发布问题,通过使用第2、3条对象的安全发布规则,双重检查也能应用于并发编程实践。