天天看點

happens-before,雙重檢查鎖定與延遲初始化,Java記憶體模型綜述--java并發程式設計的藝術(五)JMM的設計雙重檢查鎖定與延遲初始化Java記憶體模型綜述

  • JMM的設計
    • happens-before的定義
    • happens-before規則
  • 雙重檢查鎖定與延遲初始化
    • 雙重檢查鎖定的由來
    • 基于volatile的解決方案
    • 基于類初始化的解決方案
  • Java記憶體模型綜述
    • 處理器的記憶體模型
    • 各種記憶體模型之間的關系
    • JMM的記憶體可見性保證

JMM的設計

從JMM設計者的角度,在設計JMM時,需要考慮兩個關鍵因素

1)程式員對記憶體模型的使用。程式員希望記憶體模型易于了解、易于程式設計。程式員希望基于一個強記憶體模型來編寫代碼。

2)編譯器和處理器對記憶體模型的實作。編譯器和處理器希望記憶體模型對它們的束縛越少越好,這樣它們就可以做盡可能多的優化來提高性能。編譯器和處理器希望實作一個弱記憶體模型。

JMM把happens-before要求禁止的重排序分為了下面兩類

1)會改變程式執行結果的重排序。

對于會改變程式執行結果的重排序,JMM要求編譯器和處理器必須禁止這種重排序。

2)不會改變程式執行結果的重排序。

對于不會改變程式執行結果的重排序,JMM對編譯器和處理器不做要求(JMM允許這種重排序)。

happens-before,雙重檢查鎖定與延遲初始化,Java記憶體模型綜述--java并發程式設計的藝術(五)JMM的設計雙重檢查鎖定與延遲初始化Java記憶體模型綜述

JMM向程式員提供的happens-before規則能滿足程式員的需求。JMM的happens-before規則不但簡單易懂,而且也向程式員提供了足夠強的記憶體可見性保證(有些記憶體可見性保證其實并不一定真實存在。

JMM對編譯器和處理器的束縛已經盡可能少。JMM其實是在遵循一個基本原則:隻要不改變程式的執行結果(指的是單線程程式和正确同步的多線程程式),編譯器和處理器怎麼優化都行。

happens-before的定義

happens-before關系的定義如下

1)如果一個操作happens-before另一個操作,那麼第一個操作的執行結果将對第二個操作

可見,而且第一個操作的執行順序排在第二個操作之前。

2)兩個操作之間存在happens-before關系,并不意味着Java平台的具體實作必須要按照

happens-before關系指定的順序來執行。如果重排序之後的執行結果,與按happens-before關系

來執行的結果一緻,那麼這種重排序并不非法(也就是說,JMM允許這種重排序)。

as-if-serial語義保證單線程内程式的執行結果不被改變,happens-before關系保證正确同步的多線程程式的執行結果不被改變。as-if-serial語義和happens-before這麼做的目的,都是為了在不改變程式執行結果的前提下,盡可能地提高程式執行的并行度。

happens-before規則

1)程式順序規則:一個線程中的每個操作,happens-before于該線程中的任意後續操作。

2)螢幕鎖規則:對一個鎖的解鎖,happens-before于随後對這個鎖的加鎖。

3)volatile變量規則:對一個volatile域的寫,happens-before于任意後續對這個volatile域的讀。

4)傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C。

5)start()規則:如果線程A執行操作ThreadB.start()(啟動線程B),那麼A線程的ThreadB.start()操作happens-before于線程B中的任意操作。

6)join()規則:如果線程A執行操作ThreadB.join()并成功傳回,那麼線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功傳回。

雙重檢查鎖定與延遲初始化

雙重檢查鎖定的由來

在Java程式中,有時候可能需要推遲一些高開銷的對象初始化操作,并且隻有在使用這些對象時才進行初始化。在早期的JVM中,synchronized(甚至是無競争的synchronized)存在巨大的性能開銷。是以,人們想出了一個“聰明”的技巧:雙重檢查鎖定(Double-Checked Locking)。人們想通過雙重檢查

鎖定來降低同步的開銷。

基于volatile的解決方案

happens-before,雙重檢查鎖定與延遲初始化,Java記憶體模型綜述--java并發程式設計的藝術(五)JMM的設計雙重檢查鎖定與延遲初始化Java記憶體模型綜述

這個方案本質上是通過禁止圖中的2和3之間的重排序,來保證線程安全的延遲初始化。

基于類初始化的解決方案

VM在類的初始化階段(即在Class被加載後,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去擷取一個鎖。這個鎖可以同步多個線程對同一個類的初始化。基于這個特性,可以實作另一種線程安全的延遲初始化方案

Java記憶體模型綜述

處理器的記憶體模型

順序一緻性記憶體模型是一個理論參考模型,JMM和處理器記憶體模型在設計時通常會以順序一緻性記憶體模型為參照。

根據對不同類型的讀/寫操作組合的執行順序的放松,可以把常見處理器的記憶體模型劃分為如下幾種類型。

1)放松程式中寫-讀操作的順序,由此産生了Total Store Ordering記憶體模型(簡稱為TSO)。

2)在上面的基礎上,繼續放松程式中寫-寫操作的順序,由此産生了Partial Store Order記憶體模型(簡稱為PSO)。

3)在前面兩條的基礎上,繼續放松程式中讀-寫和讀-讀操作的順序,由此産生了RelaxedMemory Order記憶體模型(簡稱為RMO)和PowerPC記憶體模型

happens-before,雙重檢查鎖定與延遲初始化,Java記憶體模型綜述--java并發程式設計的藝術(五)JMM的設計雙重檢查鎖定與延遲初始化Java記憶體模型綜述

所有處理器記憶體模型都允許寫-讀重排序,它們都使用了寫緩存區。寫緩存區可能導緻寫-讀操作重排序。同時,我們可以看到這些處理器記憶體模型都允許更早讀到目前處理器的寫,原因同樣是因為寫緩存區。由于寫緩存區僅對目前處理器可見,這個特性導緻目前處理器可以比其他處理器先看到臨時儲存在自己寫緩存區中的寫。

表中的各種處理器記憶體模型,從上到下,模型由強變弱。越是追求性能的處理器,記憶體模型設計得會越弱。因為這些處理器希望記憶體模型對它們的束縛越少越好,這樣它們就可以做盡可能多的優化來提高性能。

JMM屏蔽了不同處理器記憶體模型的差異,它在不同的處理器平台之上為Java程式員呈現了一個一緻的記憶體模型。

各種記憶體模型之間的關系

JMM是一個語言級的記憶體模型,處理器記憶體模型是硬體級的記憶體模型,順序一緻性記憶體模型是一個理論參考模型。

happens-before,雙重檢查鎖定與延遲初始化,Java記憶體模型綜述--java并發程式設計的藝術(五)JMM的設計雙重檢查鎖定與延遲初始化Java記憶體模型綜述

常見的4種處理器記憶體模型比常用的3中語言記憶體模型要弱,處理器記憶體模型和語言記憶體模型都比順序一緻性記憶體模型要弱。同處理器記憶體模型一樣,越是追求執行性能的語言,記憶體模型設計得會越弱。

JMM的記憶體可見性保證

1)單線程程式。單線程程式不會出現記憶體可見性問題。編譯器、runtime和處理器會共同确

保單線程程式的執行結果與該程式在順序一緻性模型中的執行結果相同。

2)正确同步的多線程程式。正确同步的多線程程式的執行将具有順序一緻性(程式的執行

結果與該程式在順序一緻性記憶體模型中的執行結果相同)。這是JMM關注的重點,JMM通過限

制編譯器和處理器的重排序來為程式員提供記憶體可見性保證。

3)未同步/未正确同步的多線程程式。JMM為它們提供了最小安全性保障:線程執行時讀取

到的值,要麼是之前某個線程寫入的值,要麼是預設值(0、null、false)。

happens-before,雙重檢查鎖定與延遲初始化,Java記憶體模型綜述--java并發程式設計的藝術(五)JMM的設計雙重檢查鎖定與延遲初始化Java記憶體模型綜述

把前一周看的書總結了一下午加半晚上,大緻明白了很多概念,對于并發程式設計才是剛剛起步,加油!