双重检测单例的正确写法

目录

问题根源

解决方案

不允许2和3重排序

使用同步监视器允许2和3重排序,但不允许其他线程“看到”这个重排序


问题根源

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
}

new一个对象经历 的过程如下:

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

但是编译器或者cpu处理器会对以上做指令的重排序:

memory = allocate();  // 1:分配对象的内存空间
instance = memory;   // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory);  // 2:初始化对象

现在来考虑A,B 2个线程同时调用getInstance方法,A线程正要执行初始化对象时,B线程做第一次检查,可能会发现instance变量并非为null,B线程拿到了一个未初始化的对象去使用,最终会发生一些未知错误。

解决方案

不允许2和3重排序

public class SafeDoubleCheckedLocking {
private volatile static Instance instance;//volatile被JMM实现为1.保证可见性;2.禁止重排前后指令
public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance(); // instance为volatile,现在没问题了
}
}
return instance;
}
}

使用同步监视器允许2和3重排序,但不允许其他线程“看到”这个重排序

public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance ;  // 这里将导致InstanceHolder类被初始化,进而同步加锁地执行静态代码块、静态字段初始化等操作
}
}

参考:《并发编程的艺术》

发布了51 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/liao_hb/article/details/105099070