关于2022招聘季:阿里三面Java的synchronized-能防止指令重排序吗

19次阅读

共计 2679 个字符,预计需要花费 7 分钟才能阅读完成。

引言

二狗 :二胖你昨天销假了是不是又去面试了啊?
二胖 :别说了我就进来试试水,看看当初工作好不好找,顺带进来找找打击,而后能力好好静下心来好好学习。
二狗: 那被打击的怎么样啊?晓得本人是什么样的程度了吧,坏笑。
二胖 :根底太差,一面就让回去等告诉了,我要好好学习了,不跟你瞎扯了。
二狗: 都问了你什么问题啊,把你打击成这样?一起复盘下让我也好好筹备下啊。
二胖 :好吧,你既然这么好奇,那我就大略说下吧,你搬上小板凳认真挺好了哦。我要开始我的表演了。
上面二胖第一面开始了。
面试官 :二胖是吧,先做个自我介绍吧。
二胖 :好的,我叫二胖,我来自长沙,往年 25 岁,从事 java 开发快 3 年了,当初在 XX 公司 XX 事业部负责高级java 开发工程师,次要负责 XX 零碎。。。。。
面试官 :好的,我看你简历上写着熟练掌握并发编程你能跟我说说并发编程外面你都晓得哪些关键字。
二胖: 这不就是要考我 synchronizedvolatile 这个我善于啊,我特意背过的,synchronizedjava 提供的一个关键字它次要能保障原子性、有序性它的底层次要是通过 Monitor 来实现的。volatile也是 java 的一个关键字它的次要作用是能够保障可见性。。。。此处省略 1000 字。
面试官 :八股文背的不错,说了这么多,咱们来入手试试吧,写一个双重校验锁(dcl)的单例我看看。
二胖: 从屁股口袋里拿出了笔三下五除二就把它默写进去了。
面试官 :你有说道volatile 关键字和 synchronized 关键字。synchronized能够保障原子性、有序性和可见性。而 volatile 却只能保障有序性和可见性。那么,咱们再来看一下双重校验锁实现的单例,曾经应用了 synchronized,为什么还须要volatile?这个volatile 是否能够去掉?
二胖: 让我想想,貌似如同的确能够去掉。
面试官: 咱们明天的面试就到这里吧,后续有音讯人事会分割你,感激你明天来面试。

二胖很郁闷回去谷歌了下这个问题,stackoverflow上也有这个问题,看样子不只我一个人不晓得这个问题吗?看样子面试挂的不冤
以上故事纯属虚构,如有雷同请以本文为主。

synchronized 的有序性?

咱们先来看看没有加 volatile 润饰的单例:

 1   public class Singleton {  
 2      private static Singleton singleton;  
 3       private Singleton (){}  
 4       public static Singleton getSingleton() {5       if (singleton == null) {6           synchronized (Singleton.class) {7               if (singleton == null) {8                   singleton = new Singleton();  
 9               }  
 10           }  
 11       }  
 12       return singleton;  
 13       }  
 14   }  

上述代码看下来是不是感觉没啥问题。
首先咱们先来看下这一行代码到底干了哪些事件

singleton = new Singleton() 


上述过程咱们能够简化成 3 个步骤:

  • JVM为对象调配一块内存 M。
  • ②在内存 M 上为对象进行初始化。
  • ③将内存 M 的地址复制给 singleton 变量。
    这个步骤有两种执行程序能够依照 ①②③或者 ①③② 来执行。当咱们依照 ①③② 的程序来执行的时候
    咱们假如有两个线程 ThreadAThreadB 同时来申请Singleton.getSingleton 办法:
  • 失常状况依照 ①②③的程序来执行
    第一步:ThreadA 进入到第 8 行,执行 singleton = new Singleton() 进行对象的初始化(依照对象初始化的过程 ①②③)执行完。
    第二步: ThreadB进入第 5 行判断 singleton 不为空(第一步曾经初始化好了),间接返回 singleton
    第三步:拿到这个对象做其余的操作。
    这样看下来是不是没有啥问题。
  • 那如果对象初始化的时候依照 ①③② 的步骤咱们再来看看:
    第一步: ThreadA进入到第 8 行,执行 singleton = new Singleton() 执行完.①JVM为对象调配一块内存 M。③将内存的地址复制给singleton 变量。
    第二步: 此时 ThreadB 间接进入第 5 行,发现 singleton 曾经不为空了而后间接就跳转到 12 行拿到这个 singleton 返回去执行操作去了。此时 ThreadB 拿到的 singleton 对象是个半成品对象,因为还没有为这个对象进行初始化 (②还没执行)。
    第三步: 所以 ThreadB 拿到的对象去执行办法可能会有异样产生。至于为什么会这样列?《Java 并发编程实战》有提到

    有 synchronized 无 volatile 的 DCL(双重查看锁) 会呈现的状况:线程可能看到援用的以后值,但对象的状态值确少生效的,这意味着线程能够看到对象处于有效或谬误的状态。

说白了也就是 ThreadB 是能够拿到一个援用曾经有了然而内存资源还没有调配的对象。
如果要解决创建对象依照①②③的程序,其实也就是为了解决指令重排只有第 2 行加个 volatile 润饰就好。
说好的 synchronized 不是能够保障有序性的吗?volatile 的有序性?synchronized 不能不够保障指令重排吗?
怎么来定义程序呢?《深刻了解 Java 虚拟机第三版》有提到

Java 程序中人造的有序性能够总结为一句话:如果在本线程内察看,所有操作都是人造有序的。如果在一个线程中察看另一个线程,所有操作都是无序的。前半句是指“线程内似体现为串行的语义”,后半句是指“指令重排”景象和“工作内存与主内存同步提早”景象。

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

** 本文参加了 SegmentFault 思否征文「如何“反杀”面试官?」,欢送正在浏览的你也退出。
**

完结

  • 因为本人满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还望留言给我指出来, 我会对其加以修改。
  • 如果你感觉文章还不错,你的转发、分享、赞叹、点赞、留言就是对我最大的激励。
  • 感谢您的浏览, 非常欢送并感谢您的关注。

站在伟人的肩膀上摘苹果:
https://stackoverflow.com/que…
https://juejin.cn/post/684490…

正文完
 0