簡介
happens-before是JMM的核心概念,理解happens-before是了解JMM的關鍵,
?
1、設計意圖
JMM的設計需要考慮兩個方面,分別是程式員角度和編譯器、處理器角度:
- 程式員角度,希望記憶體模型易于理解、易于編程,希望是一個強記憶體模型,
- 編譯器和處理器角度,希望減少對它們的束縛,以至于編譯器和處理器可以做更多的性能優化,希望是一個弱記憶體模型,
?
因此JSR-133專家組設計JMM的核心目標就兩個:
- 為程式員提供足夠強的記憶體模型
- 對編譯器和處理器的限制盡可能少
?
下面通過一段代碼來看JSR-133如何實作這兩個目標:
double pi = 3.14; //A
double r = 1.0; //B
double area = pi * r * r //C
上述代碼存在如下happens-before關系:
- A happens-before B
- B happens-before C
- A happens-before C
這3個happens-before關系中,第二個和第三個是必須的,而第一個是非必須的(A、B操作之間重排序,程式執行結果不會發生改變),
JMM把happens-before要求禁止的重排序分為下面的兩類:
- 會改變程式執行結果的重排序
- 不會改變程式執行結果的重排序
JMM對這兩種不同性質的重排序,采取了不同的策略:
- 對于會改變程式執行結果的重排序,JMM要求編譯器和處理器必須禁止
- 對于不會改變程式執行結果的重排序,JMM不做要求(JMM運行)
?
JMM設計示意圖:

總結:
- JMM給程式員提供的happens-before規則能滿足程式員的需求,簡單易懂,具有足夠強的記憶體可見性保證,
- JMM對編譯器和處理器的束縛盡可能少,遵循的原則是:不改變程式的執行結果(正確同步或單執行緒執行),編譯器和處理器可以任意優化,
2、happens-before的定義
起源:
happens-before規則來源于Leslie Lamport《Time, Clocks and the Ordering of Events in a Distributed System》,該論文中使用happens-before來定義分布式系統中事件之間的偏序關系(partial ordering),該文中給出了一個分布式演算法,能用來將偏序關系擴展為某種全序關系,
?
Java中的應用:
JSR-133使用happens-before來指定兩個操作之間的執行順序,JMM可以通過happens-before關系向程式員提供跨執行緒的記憶體可見性保證,
?
《JSR-133:Java Memory Model and Thread Specification》對happens-before關系的定義如下:
- 如果操作A happens-before 操作B,那么A操作的執行結果將會對操作B可見,且操作A的執行順序排在操作B之前——JMM對程式員的承諾
- 兩個操作存在happens-before關系,并不意味著Java平臺的具體實作必須按照happens-before的順序來執行,如果重排序不改變程式執行結果(與happens-before)規則一致,那么這種重排序是不非法的(JMM允許這種重排序),——JMM對編譯器和處理器的束縛原則
?
happens-before和as-if-serial語意:
從上述來看,happens-before和as-if-serial語意本質上是一回事
- as-if-serial語意保證單執行緒內程式的執行結果不被改變,happens-before關系保證正確同步的多執行緒程式的執行結果不改變
- as-if-serial語意給編程者一種單執行緒是按程式順序執行的幻境;happens-before關系給編程者一種正確同步的多執行緒是按照happens-before指定的順序執行的幻境,
兩者的目的都是為了在不改變程式執行結果的前提下,盡可能的提高程式的執行效率,
?
3、happens-before規則
《JSR-133:Java Memory Model and Thread Specification》定義了如下happens-before規則
- 程式順序規則
- 監視器鎖規則
- volatile變數規則
- 傳遞性
- start()規則
- join()規則
?
3.1 volatile寫-讀
volatile寫-讀建立的happens-before關系

分析上圖:
- 1 happens-before 2和3 happens-before 4由程式順序規則產生,由于編譯器和處理器遵循as-if-serial語意,也就是說,as-if-serial語意保證了程式順序規則,因此可以把程式順序規則看成是對as-if-serial語意的“封裝”,
- 2 happens-before 3 是有volatile規則產生,一個volatile變數的讀,總是能看到(任意執行緒)對這個volatile變數的最后寫入,
- 1 happens-before 4 是由傳遞性規則產生的,這里的傳遞性是由volatile的記憶體屏障插入策略和volatile的編譯器重排序規則來共同保證的,
?
?
3.2 start()規則
假設執行緒A在執行的程序中,通過執行ThreadB.start()來啟動執行緒B;同時,假設執行緒A在執行ThreadB.start()之前修改了一個共享變數,執行緒B在執行后會讀取這些共享變數,
start()程式對應的happens-before關系圖:

分析上圖:
- 1 happens-before 2 由程式順序規則產生
- 2 happens-before 4 由start規則產生
- 1 happens-before 4 由傳遞性規則產生
因此執行緒A執行ThreadB.start()之前對共享變數所做的修改,在執行緒B執行后都將確保對執行緒B可見,
?
3.3 join()規則
假設執行緒A執行的程序中,通過執行ThreadB.join()來等待執行緒B終止;則執行緒B在終止之前修改了一些共享變數,執行緒A從ThreadB.join()回傳后會讀這些共享變數,
join()程式的happens-before關系圖:
?
分析上圖:
- 2 happens-before 4 由join()規則產生
- 4 happens-before 5 由程式順序規則產生
- 2 happens-before 5 由傳遞性規則產生
因此執行緒A執行操作ThreadB.join()并成功回傳,執行緒B中任意操作都將對執行緒A可見,
?
?
文章總結至《Java并發編程藝術》,下篇總結“雙重檢查所定與延遲初始化”,敬請關注,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287338.html
標籤:其他
上一篇:畢業季 | 令人難以忘懷的事和人
下一篇:C語言指標系列(一)
