阿里面試:Java的synchronized 能防止指令重排序嗎?我猶豫了

引言

二狗:二胖你昨天請假了是否是又去面試了啊?
二胖:別說了我就出去試試水,看看如今工做好很差找,順帶出去找找打擊,而後才能好好靜下心來好好學習。
二狗: 那被打擊的怎麼樣啊?知道本身是什麼樣的水平了吧,壞笑。
二胖:基礎太差,一面就讓回去等通知了,我要好好學習了,不跟你瞎扯了。
二狗: 都問了你什麼問題啊,把你打擊成這樣?一塊兒覆盤下讓我也好好準備下啊。
二胖:好吧,你既然這麼好奇,那我就大概說下吧,你搬上小板凳仔細挺好了哦。我要開始個人表演了。
下面二胖第一面開始了。
面試官:二胖是吧,先作個自我介紹吧。
二胖:好的,我叫二胖,我來自長沙,今年25歲,從事java開發快3年了,如今在XX公司XX事業部擔任高級java開發工程師,主要負責XX系統。。。。。
面試官:好的,我看你簡歷上寫着熟練掌握併發編程你能跟我說說併發編程裏面你都知道哪些關鍵字。
二胖: 這不就是要考我 synchronizedvolatile 這個我擅長啊,我特地背過的,synchronizedjava提供的一個關鍵字它主要能保證原子性、有序性它的底層主要是經過Monitor來實現的。volatile也是java的一個關鍵字它的主要做用是能夠保證可見性。。。。此處省略1000字。
面試官:八股文背的不錯,說了這麼多,咱們來動手試試吧,寫一個雙重校驗鎖(dcl)的單例我看看。
二胖: 從屁股口袋裏拿出了筆三下五除二就把它默寫出來了。
面試官:你有說道volatile關鍵字和synchronized關鍵字。synchronized能夠保證原子性、有序性和可見性。而volatile卻只能保證有序性和可見性。那麼,咱們再來看一下雙重校驗鎖實現的單例,已經使用了synchronized,爲何還須要volatile?這個volatile是否能夠去掉?
二胖: 讓我想一想,貌似好像確實能夠去掉。
面試官: 咱們今天的面試就到這裏吧,後續有消息人事會聯繫你,感謝你今天來面試。java

二胖很鬱悶回去谷歌了下這個問題,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個步驟:ide

  • 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(雙重檢查鎖) 會出現的狀況:線程可能看到引用的當前值,但對象的狀態值確少失效的,這意味着線程能夠看到對象處於無效或錯誤的狀態。post

說白了也就是ThreadB是能夠拿到一個引用已經有了可是內存資源尚未分配的對象。
若是要解決建立對象按照①②③的順序,其實也就是爲了解決指令重排只要第2行加個volatile修飾就好。
說好的synchronized 不是能夠保證有序性的嗎?volatile的有序性?synchronized 不能不夠保證指令重排嗎?
怎麼來定義順序呢?《深刻理解Java虛擬機第三版》有提到學習

Java程序中自然的有序性能夠總結爲一句話:若是在本線程內觀察,全部操做都是自然有序的。若是在一個線程中觀察另外一個線程,全部操做都是無序的。前半句是指「線程內似表現爲串行的語義」,後半句是指「指令重排」現象和「工做內存與主內存同步延遲」現象。優化

  • synchronized 的有序性是持有相同鎖的兩個同步塊只能串行的進入,即被加鎖的內容要按照順序被多個線程執行,可是其內部的同步代碼仍是會發生重排序,使塊與塊之間有序可見。
  • volatile的有序性是經過插入內存屏障來保證指令按照順序執行。不會存在後面的指令跑到前面的指令以前來執行。是保證編譯器優化的時候不會讓指令亂序。
  • synchronized 是不能保證指令重排的

    結束

  • 因爲本身才疏學淺,不免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 若是你以爲文章還不錯,你的轉發、分享、讚揚、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。
    在這裏插入圖片描述

站在巨人的肩膀上摘蘋果:
https://stackoverflow.com/questions/7855700/why-is-volatile-used-in-double-checked-locking
https://juejin.cn/post/6844903920599302152線程

相關文章
相關標籤/搜索