synchronized能防止指令重排序吗?

疑问

volatile关键字和synchronized关键字。

synchronized可以保证原子性、有序性和可见性。

volatile却只能保证有序性和可见性。

注意:这两个有序的意思是不一样的。

那么,我们再来看一下双重校验锁实现的单例,已经使用了synchronized,为什么还需要volatile?这个volatile是否可以去掉?

public class SingleInstance{

    // 使用 volatile 禁止 new 对象时进行指令重排
    // new 对象,有三条指令完成
    // 1 JVM为对象分配一块内存M在堆区
    // 2 在内存M上为对象进行初始化
    // 3 将内存M的地址复制给instance变量
    private volatile static SingleInstance instance;

    private SingleInstance(){
        // 反射可以直接调用构造方法,绕过private的限制
        // 这里进行避免
        // 反序列化咋搞?
        if(instance != null){
            throw new RuntimeException("single instance existed");
        }
    }

    public SingaleInstance getInstance(){
        // 减少锁竞争,避免过多的线程进入同步队列,进入 blocking 状态
        if(instance == null){
            // 保证只有一个线程可以实例化对象,其他线程进入同步队列blocking
            // 这个 syn 可以保证原子性和可见性
            // 而 有序性,指的是 保证线程串行进入syn 代码块内
            // 所以,此有序性无法保证 syn代码块 内部的有序性
            synchronized(SingleInstance.class){
                // 避免重复创建对象
                if(instance == null){
                    instance = new SingleInstance();
                }
            }
        }
        return instance;
    }    
}

由于 synchronized 是不能保证指令重排的,所以,可能会出问题。

new 对象的三个步骤(简化)

  1. JVM为对象分配一块内存M。
  2. 在内存M上为对象进行初始化。
  3. 将内存M的地址复制给singleton变量。

这个步骤有两种执行顺序可以按照 ①②③或者①③②来执行。

当我们按照①③②的顺序来执行的时候:

我们假设有两个线程ThreadAThreadB同时来请求SingleInstance.getInstance方法:

正常情况按照 ①②③的顺序来执行

  • ThreadA 进入到同步代码块,执行 instance = new SingleInstance() 进行对象的初始化(按照对象初始化的过程 ①②③)执行完。
  • ThreadB进入第5行判断instance不为空(已经初始化好了),直接返回instance
  • 拿到这个对象做其他的操作。

这样看下来是不是没有啥问题。

但是,如果对象初始化的时候按照 ①③② 的步骤我们再来看看:

  • ThreadA进入到同步代码块,执行 instance = new SingleInstance() 执行完; ①JVM为对象分配一块内存M。③将内存的地址复制给singleton变量。
  • 此时ThreadB直接进入到同步代码块,发现instance已经不为空了然后直接就跳转到12行拿到这个instance返回去执行操作去了。此时ThreadB拿到的singleton对象是个半成品对象,因为还没有为这个对象进行初始化(②还没执行)。
  • 所以, ThreadB拿到的对象去执行方法可能会有异常产生。至于为什么会这样列?《Java 并发编程实战》有提到

有 synchronized 无 volatile 的 DCL(双重检查锁) 会出现的情况:线程可能看到引用的当前值,但对象的状态值确少失效的,这意味着线程可以看到对象处于无效或错误的状态。

说白了也就是ThreadB是可以拿到一个引用已经有了但是内存资源还没有分配的对象。

解决

解决指令重排只要给 instance 加个volatile修饰就好

synchronizedvolatile的有序性比较

  • synchronized 的有序性:是持有相同锁的两个同步块只能串行的进入,即被加锁的内容要按照顺序被多个线程执行,但是其内部的同步代码还是会发生重排序,使块与块之间有序可见。
  • volatile的有序性:是通过插入内存屏障来保证指令按照顺序执行。不会存在后面的指令跑到前面的指令之前来执行。是保证编译器优化的时候不会让指令乱序。

附录

https://blog.csdn.net/zengfanwei1990/article/details/110245035

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值