双重检验锁模式为什么要使用volatile?
并发编程情况下有三个要点:操作的原子性、可见性、有序性。
volatile保证了可见性和有序性,但是并不能保证原子性。
首先看一下DCL(双重检验锁)的实现:
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
无论是volatile修饰singleton 还是 synchronized 还是两次是否未null的判断,都是为了保证 new 操作的正常进行。
所以new操作的背后到底有什么秘密的?
new 实例背后的指令
从字节码可以看到创建一个对象实例,可以分为三步:
- 分配对象内存
- 调用构造器方法,执行初始化
- 将对象引用赋值给变量。
虚拟机实际运行时,以上指令可能发生重排序。以上代码 2,3 可能发生重排序,但是并不会重排序 1 的顺序。也就是说 1 这个指令都需要先执行,因为 2,3 指令需要依托 1 指令执行结果。
虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。
在JIT里,可能会将2与3进行重排序,在单线程里这里并不会发生什么问题,但是在多线程情况下,会出现下面的问题
时间 |
线程A |
线程B |
t1 |
A1:分配对象的内存空间 |
|
t2 |
A2:设置singleton指向内存空间 |
|
t3 |
B1:判断singleton是否为空 |
|
t4 |
B2:由于singleton不为null,线程B将访问singleton引用的对象 |
|
t5 |
A3:初始化该对象(使得singleton不为空) |
|
t6 |
A4:访问singleton引用的对象 |
A2与A3重排序后,会让线程B在B1处判断出singleton不为null,线程B接下来将访问的singleton引用的对象是一个未初始化的对象。
所以用volatile修饰 singleton来就是禁止2与3的重排序,来保证线程安全的延迟初始化。
参考链接:https://blog.csdn.net/OrangeRawNorthland/article/details/83788412
你为什么不努力。。