Java的synchronized 能防止指令重排序嗎?
引言
「二狗」:二胖你昨天請假了是不是又去面試了啊?
「二胖」:別說了我就出去試試水,看看現在工作好不好找,順帶出去找找打擊,然后才能好好靜下心來好好學習。
「二狗:」 那被打擊的怎么樣啊?知道自己是什么樣的水平了吧,壞笑。
「二胖」:基礎太差,一面就讓回去等通知了,我要好好學習了,不跟你瞎扯了。
「二狗:」 都問了你什么問題啊,把你打擊成這樣?一起復盤下讓我也好好準備下啊。
「二胖」:好吧,你既然這么好奇,那我就大概說下吧,你搬上小板凳仔細挺好了哦。我要開始我的表演了。
下面二胖第一面開始了。
「面試官」:二胖是吧,先做個自我介紹吧。
「二胖」:好的,我叫二胖,我來自長沙,今年25歲,從事java開發快3年了,現在在XX公司XX事業部擔任高級「java」開發工程師,主要負責XX系統。。。。。
「面試官」:好的,我看你簡歷上寫著熟練掌握并發編程你能跟我說說并發編程里面你都知道哪些關鍵字。
「二胖:」 這不就是要考我 synchronized 和volatile 這個我擅長啊,我特意背過的,synchronized 是java提供的一個關鍵字它主要能保證原子性、有序性它的底層主要是通過Monitor來實現的。volatile也是java的一個關鍵字它的主要作用是可以保證可見性。。。。此處省略1000字。
「面試官」:八股文背的不錯,說了這么多,我們來動手試試吧,寫一個雙重校驗鎖(dcl)的單例我看看。
「二胖:」 從屁股口袋里拿出了筆三下五除二就把它默寫出來了。
「面試官」:你有說道volatile關鍵字和synchronized關鍵字。synchronized可以保證原子性、有序性和可見性。而volatile卻只能保證有序性和可見性。那么,我們再來看一下雙重校驗鎖實現的單例,已經使用了synchronized,為什么還需要volatile?這個volatile是否可以去掉?
「二胖:」 讓我想想,貌似好像確實可以去掉。
「面試官:」 我們今天的面試就到這里吧,后續有消息人事會聯系你,感謝你今天來面試。
二胖很郁悶回去谷歌了下這個問題,「stackoverflow」上也有這個問題,看樣子不只我一個人不知道這個問題嗎?看樣子面試掛的不冤「以上故事純屬虛構,如有雷同請以本文為主?!?/p>
synchronized 的有序性?
我們先來看看沒有加volatile 修飾的單例:
- public class Singleton {
- private static Singleton singleton;
- private Singleton (){}
- public static Singleton getSingleton() {
- if (singleton == null) {
- synchronized (Singleton.class) {
- if (singleton == null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
- }
上述代碼看下來是不是感覺沒啥問題。首先我們先來看下這一行代碼到底干了哪些事情
- singleton = new Singleton()
上述過程我們可以簡化成3個步驟:
①「JVM」為對象分配一塊內存M。
②在內存M上為對象進行初始化。
③將內存M的地址復制給singleton變量。
這個步驟有兩種執行順序可以按照 「①②③」或者「①③②」來執行。當我們按照「①③②」的順序來執行的時候 我們假設有兩個線程ThreadA 和ThreadB 同時來請求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 是不能保證指令重排的」。
結束
由于自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
本文轉載自微信公眾號「 java金融」,可以通過以下二維碼關注。轉載本文請聯系 java金融公眾號。



































