Java內存模型(JMM)詳述

1.內存模型的抽象結構

抽象角度:JMM定義了主內存和線程之間的抽象關係。
線程之間的共享變量存儲在主內存,每個線程都有本地內存(是JMM的一個抽象概念,實際不存在),本地內存存儲了共享變量的副本。

這裏寫圖片描述

2.內存模型的類型

  • TSO (Total Store Ordering)放鬆寫讀的順序
  • PSO (Partial Store Order)(在TSO基礎上繼續放鬆寫寫的順序)
  • RMO(Relaxed Memory Order)(在PSO基礎上繼續放鬆讀寫和讀讀的順序)
  • PowerPC

從上到下,模型由強變弱。越是追求性能的處理器,內存模型設計得會越弱。

這裏寫圖片描述

3.happens-before

Jdk1.5開始,java使用jsr-133內存模型,JSR-133使用happens-before來闡述內存可見性。

happens-before 規則如下:

1)程序順序規則:一個線程中的每個操作,happens-before於後續操作。
2)監視器鎖規則:一個鎖的解鎖happens-before於對這個鎖的加鎖。
3)volatile變量規則:對一個volatile變量的寫happens-before於讀
4)傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C
5)start()規則:線程A的ThreadB.start()操作happens-before於線程B的任意操作。
6)Join() 規則:線程A的執行ThreadB.join()操作,線程B的任意操作happens-before於ThreadB.join()成功返回。

注意:兩個操作之間具有happens-before關係,並不是前一個操作必須要在後一個操作之前執行,而是前一個操作(執行的結果)對後一個操作可見,且前一個操作按順序排在第二個操作之前。

4.順序一致性模型

如果程序是正確同步的,程序的執行結果和在順序一致性內存模型中結果一樣。

  • 同步原語(synchronized,volatile,final)

  • 順序一致性內存模型是一個理想參考模型。

  • 特性:
    1)一個線程所有操作按照程序的順序執行。
    2)每個操作都必須原子執行,並且立刻對所有線程可見。

  • 同步程序的順序一致性效果

JMM中,臨界區內的代碼可以重排序(但JMM不允許臨界區內的代碼“逸出”到臨界區之外,那樣會破壞監視器的語義)。由於監視器互斥執行,線程B無法看到線程A在臨界區的重排序,所以結果和順序一致性模型中一樣。

5.指令重排序

爲了提高性能,編譯器和處理器常常會對指令做重排序。

5.1 重排序分3種類型

1)編譯器優化的重排序。編譯器在不改變語義的前提,可以安排語句的執行順序。
2)指令級並行的重排序。處理器可以採用指令並行執行的技術,改變原有語句的執行順序。
3)內存系統的重排序。由於處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操作看上去可能是在亂序執行。

這裏寫圖片描述

對於編譯器重排序,JMM的編譯器重排序規則會禁止一部分的編譯器重排序
對於處理器重排序,JMM採用 插入內存屏障指令 禁止一些處理器重排序。

5.2 重排序遵守的規則

(1)數據依賴性(單線程),不會改變存在數據依賴性的兩個操作的執行順序
這裏寫圖片描述

(2)as-if-serial,意思是不管怎麼重排序,(單線程)程序的執行結果不能被改變

(3)在不改變結果的時候,儘可能提高並行度。

5.3 重排序對多線程的影響

例如如下代碼中,操作1和操作2沒有數據依賴關係,可以進行重排序;操作3和操作4沒有數據依賴關係,也可以重排序。所以程序執行結果就會產生不一致。
這裏寫圖片描述

6. 內存屏障

內存屏障(memory barriers,或者叫內存柵欄memory fence):
爲了保證內存可見性,會在指令的適當位置插入內存屏障指令,禁止一些指令重排序。

6.1 內存屏障分爲

(1)LoadLoad屏障
Load1; LoadLoad; Load2。
在load2 讀取數據前,保證load1讀取數據完畢。

(2)StoreStore屏障
Store1; StoreStore; Store2
在store2 寫入前,保證Store1 已經寫入。

(3)LoadStore屏障
Load1; LoadStore; Store2
在store2 寫入之前,load1 已經讀取。

(4)StoreLoad屏障(開銷最大,兼具其它三個功能)
Store1; StoreLoad; Load2
在load2讀取之前,store1 寫入,對所有處理器可見。

這裏寫圖片描述
這裏寫圖片描述

7.final域內存語義

這裏寫圖片描述

  • 寫final域的重排序規則
    會在final域的寫之後,構造函數return之前插入一個StoreStore屏障。禁止編譯器把final域的寫重排序到構造函數之外
    比如在初始化這個對象的時候,final域的值被限定在構造函數之內。所以可以正確讀取final變量的值。
    而普通域可能重排序到了構造函數之外。就讀不到普通變量的初始值了。
    這裏寫圖片描述

  • 讀final域的重排序規則
    編譯器會在讀對象引用和讀final域之間插入LoadLoad屏障,禁止它們之間的重排序。而普通域讀沒有限制,可以重排序在讀對象引用之前。
    這裏寫圖片描述

  • Final域是引用類型
    由之前final域寫操作重排序規則知道1(final域寫)在3之前。而這裏2(final引用的對象的成員域的寫入)也是在3前;讀final域一定在讀引用對象後,所以6在3之後;但是線程B對final對象的成員域的修改,和C讀final域之間沒有重排序規則。B和C是存在數據競爭。
    這裏寫圖片描述


參考: java併發編程的藝術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章