再聊volatile关键字:指令重排与内存可见性

上了重量级的synchronized不一定万事大吉,也有可能出并发问题,跟volatile比没有强弱之分,有时候需要互补。

先看一段代码:

public class DoubleCheckedLocking {
    private static DoubleCheckedLocking resource;
    
    public static DoubleCheckedLocking getResource(){
        if(resource == null){
            synchronized(DoubleCheckedLocking.class){
                if(resource == null)    //二次check,防止lock之前的第一次check以后这段,被其他线程new了
                    resource = new DoubleCheckedLocking();
            }
        }
        return resource;
    }
}

修正后:

public class DoubleCheckedLocking {
    // volatile 保障可见性,禁止指令重排
    private static volatile DoubleCheckedLocking resource;
    // 单例,private掉构造函数
    private DoubleCheckedLocking(){}
    
    public static DoubleCheckedLocking getResource(){
        if(resource == null){
            synchronized(DoubleCheckedLocking.class){
                if(resource == null)
                    resource = new DoubleCheckedLocking();
            }
        }
        return resource;
    }
}

resource = new DoubleCheckedLocking() 是非原子的
分为3步,分配内存、创建对象、变量指向内存地址。但 JMM(Java 内存模型)允许编译器和处理器对不存在依赖关系的指令进行重排序。
比如可能会变成:分配内存、变量指向内存地址、创建对象。

1、线程 B 在第一个 if (resource == null) 判断时,看到 resource != null,于是直接 return resource,就会拿到一个未初始化完成的对象。

2、改了之后,加了volatile ,禁止指令重排,线程 B 在第一个 if (resource == null) 判断时,如果看到 resource != null,意味着resource一定完成了初始化,
(禁止指令重牌:分配内存、创建对象、变量指向内存地址,按序发生。非null说明最后一步也做完了)

synchronized 在这里的作用:保证不会有两个线程同时去new,即互斥。
但不保证指令不重排和这个场景下的可见性。
它的可见性是有条件的:线程B只有获取锁,才能看到之前线程A所做的修改。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容