synchronized能防止指令重排序吗?

导读:本篇文章讲解 synchronized能防止指令重排序吗?,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

疑问

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/69698.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!