单例模式双重校验为什么要加volatile关键字??


public class TestSingleton {

    private TestSingleton() {
    }

    private static volatile TestSingleton instance = null;

    public static TestSingleton getInstance() {
           if (instance == null) {  
             synchronized (TestSingleton.class) {  
                if (instance == null) {  
                   instance = new TestSingleton(); 
                }  
             }  
           } 
           return instance;
    }
}

看很多文章都说是volatile为了防止new TestSingleton()的指令重排序,可这段代码在synchronized代码块中,synchronized又完成可以替代volatile,所以为什么要加volatile呢??

volatile关键字是防止编译器对这个变量进行优化,造成意想不到的后果

因为在多处理器的情况下,jvm不能保证比较判断的原子性。所以可能让单例还是创建出多个实例来。

synchronized关键字只能保证单线程有序性,实际上是无法禁止指令重排序的,因此会出现返回的对象是null的情况,必须使用volatile关键字禁止指令重排序
可以看看https://www.jianshu.com/p/163f8d4986eb和https://www.jianshu.com/p/b30a4d568be4

理想情况下,第13行代码 instance = new TestSingleton(); 的执行顺序是1.先new一个对象,2.调用构造方法初始化对象,3.然后把这个对象跟变量关联起来,然后另一个线程进来执行到第十行的时候 判断instance此时不是null了,就返回了。这是理想情况。
特殊情况下,JIT会发生指令重排的情况,导致 第13行代码 instance = new TestSingleton(); 发生指令重排,执行顺序发生了改变,变成 1.先new一个对象,2.把这个对象跟变量关联起来 3.调用构造方法初始化。此时如果t1线程在刚好执行完第二步(把这个对象跟变量关联起来),还没有执行第三步(调用构造方法初始化),这是来了个t2线程,执行到第十行 if (instance == null) 的时候,这个对象不是null了 但是没有调用构造方法初始化,那么这个t2线程获取到的对象是不是有问题的?

加了volatile,可以防止这种情况的发生,这里有两方面的原因
1.内存屏障 保证写屏障之前的指令不会被重排到写屏障之后
2.由于有内存屏障,保证 调用构造方法初始化对象 的指令一定不会被重排到 然后把这个对象跟变量关联起来 之后

这样就能保证t2在能获取到对象的情况下,这个对象一定是初始化过的!