美文网首页
使用了双重检查锁为什么同时还要加上volatile ?

使用了双重检查锁为什么同时还要加上volatile ?

作者: 晨暮时代 | 来源:发表于2019-05-15 11:31 被阅读0次

结论
使用 volatile 是为了禁止重排序,因为不然的话同步代码块内在实例化对象的时候可能发生重排序,导致多线程环境下获取一个不正确的对象

推论
示例:

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

在原名为《java 单例模式中双重检查锁定 volatile 的作用》的知乎问题中,陈鹏大佬的回答是:

主要是禁止重排序,初始化一个实例(SomeType st = new SomeType())在java字节码中会有4个步骤:

1、申请内存空间
2、初始化默认值(区别于构造器方法的初始化)
3、执行构造器方法
4、连接引用和实例

这4个步骤后两个有可能会重排序,1234或1243都有可能,造成未初始化完全的对象发布。volatile可以禁止指令重排序,从而避免这个问题。

作者:陈鹏
链接:https://www.zhihu.com/question/56606703/answer/149894860
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

接着又自评道:

synchronized 禁止重排序是通过内存屏障实现的, 简单来说, 内存屏障只保证指令不会越过该屏障, 而synchronized块内部的指令仍然有可能发生重排序

【标注】我仍旧无法理解“内存屏障只保证指令不会越过该屏障”代表着什么意思

假设需要实例化的对象instance尚未使用volatile修饰,如果按照上诉回答中的1243顺序,那么有可能导致的是:A线程获取锁,接着执行对UniqueInstance对象的一系列实例化操作,然而在执行构造器方法之前,先一步连接引用和实例,然后释放锁(really???);随后B线程获取锁,发现null == instance为false,于是满足地离开,并试图访问instance实例内部的变量,却发现根本没有理想的值(这些值原本在构造器内定义)然后抛错,因为此时instance实例的构造器方法尚未执行完成。
而如果instance变量采用了volatile修饰,则可以保证1234的顺序执行,则会在连接引用和实例之前,就会完成构造器方法的执行,使得后面的B线程获得一个完整的对象。

以上只是给标题的一个满足结果的猜测(以结果倒推过程,而非以过程推论结果),文中打3个问号的地方本地始终无法验证。以上仅作参考,无需当真,就当抛砖引玉,如有质疑,还能促使你去查阅其他多方资料,也算有点用处了,哈哈!

关于synchronized对于有序性的问题,参考https://www.hollischuang.com/archives/2637中【synchronized与有序性】部分

相关文章

网友评论

      本文标题:使用了双重检查锁为什么同时还要加上volatile ?

      本文链接:https://www.haomeiwen.com/subject/jstpaqtx.html