本篇文章從happens-before定義、用途以及具體規則三個方面對happens-before進行解讀,並通過源碼案例深入了解為什麼需要happens-before規則和什麼是指令重排序。
什麼是happens-before
happens-before:A happens-before B就是A先行發生於B(這種說法不是很準確),定義為hb(A, B)。在Java內存模型中,happens-before的意思是前一個操作的結果可以被後續操作獲取。
為什麼需要happens-before
JVM會對代碼進行編譯優化,會出現指令重排序情況,為了避免編譯優化對並發編程安全性的影響,需要happens-before規則定義一些禁止編譯優化的場景,保證並發編程的正確性。以雙重檢查單例示例進行分析:
上述代碼中instance = new LazyDoubleCheckSingleton()並不是原子操作 ,JVM會分解成以下幾個命令執行:
給對象分配內容初始化對象將初始化對象和內存地址建立關聯按照上面的分解順序(1->2->3)執行不存在任何問題,但是由於JVM編譯優化的存在,可能導致2和3步驟顛倒,即按1->3->2順序執行(這就是指令重排序)。按照1->3->2順序執行,在多線程環境中執行getInstance就有可能出現instance已經和初始對象內存建立關聯,但是對象還沒有初始化完成的情況,即執行if (instance == null)的時候instance != null 直接返回沒有初始化完成的instance,導致再使用instance實例的時候報錯。volatile關鍵字是可以解決指令重排序問題的一種方式,具體解決方式如下:
有哪些happens-before規則
程序次序規則:在一個線程內一段代碼的執行結果是有序的。就是還會指令重排,但是隨便它怎麼排,結果是按照我們代碼的順序生成的不會變。管程鎖定規則:就是無論是在單線程環境還是多線程環境,對於同一個鎖來說,一個線程對這個鎖解鎖之後,另一個線程獲取了這個鎖都能看到前一個線程的操作結果!(管程是一種通用的同步原語,synchronized就是管程的實現)volatile變量規則:就是如果一個線程先去寫一個volatile變量,然後一個線程去讀這個變量,那麼這個寫操作的結果一定對讀的這個線程可見。線程啟動規則:在主線程A執行過程中,啟動子線程B,那麼線程A在啟動子線程B之前對共享變量的修改結果對線程B可見。線程終止規則:在主線程A執行過程中,子線程B終止,那麼線程B在終止之前對共享變量的修改結果在線程A中可見。也稱線程join()規則。線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程代碼檢測到中斷事件的發生,可以通過Thread.interrupted()檢測到是否發生中斷。傳遞性規則:這個簡單的,就是happens-before原則具有傳遞性,即hb(A, B) , hb(B, C),那麼hb(A, C)。對象終結規則:這個也簡單的,就是一個對象的初始化的完成,也就是構造函數執行的結束一定 happens-before它的finalize()方法。END
筆者是一位熱愛網際網路、熱愛網際網路技術、熱於分享的年輕人,如果您跟我一樣,我願意成為您的朋友,分享每一個有價值的知識給您。喜歡作者的同學,點讚+轉發+關注哦!