精進之路之JMM
JMM (Java Memory Model) java記憶體模型
Java記憶體模型的抽象
Java執行緒之間的通訊由Java記憶體模型(本文簡稱為JMM)控制,JMM決定一個執行緒對共享變數的寫入何時對另一個執行緒可見。
Java記憶體模型規定了所有的變數都儲存在主記憶體(Main Memory)中。每條執行緒還有自己的工作記憶體(Working Memory,可與前面講的處理器快取記憶體類比),執行緒的工作記憶體中儲存了被該執行緒使用到的變數的主記憶體副本拷貝,執行緒對變數的所有操作(讀取、賦值等)都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數。不同的執行緒之間也無法直接訪問對方工作記憶體中的變數,執行緒間變數值的傳遞均需要通過主記憶體來完成,執行緒、主記憶體、工作記憶體三者的互動關係如圖所示。
本地記憶體是JMM的一個抽象概念,並不真實存在。它涵蓋了快取,寫緩衝區,暫存器以及其他的硬體和編譯器優化
1. 重排序
為了程式能夠更高效的執行,編譯器和處理器都會對指令進行重排序;重排序分為以下三種類型:
- 編譯器優化的重排序
- 指令級並行的重排序
- 記憶體系統的重排序
只要是重排序都有可能會導致多執行緒內出現記憶體可見性的問題
1.1 記憶體屏障指令
為了保證記憶體可見性,java編譯器在生成指令序列的適當位置會插入記憶體屏障指令來禁止特定型別的處理器重排序。JMM把記憶體屏障指令分為下列四類:
屏障型別 | 指令示例 | 說明 |
---|---|---|
LoadLoad Barriers | Load1; LoadLoad; Load2 | 確保Load1資料的裝載,之前於Load2及所有後續裝載指令的裝載。 |
StoreStore Barriers | Store1; StoreStore; Store2 | 確保Store1資料對其他處理器可見(重新整理到記憶體),之前於Store2及所有後續儲存指令的儲存。 |
LoadStore Barriers | Load1; LoadStore; Store2 | 確保Load1資料裝載,之前於Store2及所有後續的儲存指令重新整理到記憶體。 |
StoreLoad Barriers | Store1; StoreLoad; Load2 | 確保Store1資料對其他處理器變得可見(指重新整理到記憶體),之前於Load2及所有後續裝載指令的裝載。StoreLoad Barriers會使該屏障之前的所有記憶體訪問指令(儲存和裝載指令)完成之後,才執行該屏障之後的記憶體訪問指令。 |
1.2 happens-before
如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係。這裡提到的兩個操作既可以是在一個執行緒之內,也可以是在不同執行緒之間。
happens-before規則如下:
- 程式順序規則:一個執行緒中的每個操作,happens- before 於該執行緒中的任意後續操作。
- 監視器鎖規則:對一個監視器鎖的解鎖,happens- before 於隨後對這個監視器鎖的加鎖。
- volatile變數規則:對一個volatile域的寫,happens- before 於任意後續對這個volatile域的讀。
- 傳遞性:如果A happens- before B,且B happens- before C,那麼A happens- before C。
注意,兩個操作之間具有happens-before關係,並不意味著前一個操作必須要在後一個操作之前執行!happens-before僅僅要求前一個操作(執行的結果)對後一個操作可見,且前一個操作按順序排在第二個操作之前
2. 順序一致性模型
JMM對正確同步的多執行緒程式,其執行將具有順序一致性(sequentially consistent)---即程式的執行結果和在順序一致性模型中得到的結果相同;(這裡同步是指廣義上的同步,包括同步原語(lock,volatile,final)的正確使用)
特性:
- 一個執行緒中所有操作都必須按照程式的順序來執行
- 不管程式是否同步,所有執行緒都只能看到一個單一的操作執行順序。在此模型下,每個操作都必須原子執行且立即對所有執行緒可見