双重检查锁实现单例模式的线程安全问题
Posted -marksman
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双重检查锁实现单例模式的线程安全问题相关的知识,希望对你有一定的参考价值。
一、结论
双重校验锁的单例模式代码如下:
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) { // 1
synchronized (Singleton.class) { // 2
if (singleton == null) { // 3
singleton = new Singleton(); // 4
}
}
}
return singleton;
}
}
假设有两个线程AB同时访问上面这段代码,它并不能保证线程安全。
二、问题说明
1、指令重排序的简单说明
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
(1)编译器指令重排序
编译器在不改变程序 执行结果的前提下,可以对程序的执行顺序进行优化重新排序
(2) 处理器指令重排序
参考:https://blog.csdn.net/javazejian/article/details/72772461 (处理器指令重排)
2、 对象创建过程
分为三步,如下图:
我们认为程序应该是按照1、2、3的步骤走下去,但实际上可能不是这样的,这里编译器和处理器可能会对2、3步的执行顺序进行重排序,即先将对象的引用指向内存空间,实际上A线程返回的是没有初始化的对象,然后B线程访问上面这段代码,判断if (singleton == null) { // 1 就为false,它会认为Singleton类已经实例化,问题就出在这里。
重排序后A 、B线程执行时序图如下:
三、解决方案
1、不允许对象创建过程中2、3步发生指令重排序 (基于volatile的解决方案)
即将Singleton声明时加上volatile,volatile关键字可以保证内存可见性和禁止指令重排序,关于volatile参见https://blog.csdn.net/javazejian/article/details/72772461 (volatile内存语义)
修改后的代码:
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) { // 1
synchronized (Singleton.class) { // 2
if (singleton == null) { // 3
singleton = new Singleton(); // 4
}
}
}
return singleton;
}
}
Singleton属性被加上volatile后,4中对象创建过程的2、3两步在多线程环境下就被禁止重排序,这样就能保证线程安全。
2、允许对象创建过程中2、3重排序,但不允许其他线程看到这个重排序 (基于类初始化的解决方案)
JVM在类的初始化阶段,会执行类的初始化。在执行类的初始化期间,JVM会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。基于这个特性修改代码如下:
public class Singleton {
private static class SingletonHolder{
public static Singleton singleton = new Singleton();
}
public static Singleton getSingleton(){
return SingletonHolder.singleton;
}
}
多线程访问上面这段程序的时序图如下:
参考资料:
1、https://blog.csdn.net/javazejian/article/details/72772461
2、《Java并发编程的艺术》第三章 Java内存模型
说明:菜鸟一枚,第一次发技术博客,如有错误或者写的不好的地方欢迎大家指正。
以上是关于双重检查锁实现单例模式的线程安全问题的主要内容,如果未能解决你的问题,请参考以下文章