欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java 单例模式中双重检查锁同时使用 volatile 的作用?

程序员文章站 2024-02-29 18:32:04
...

结论:使用 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块内部的指令仍然有可能发生重排序

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

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

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

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