標籤:設值 結構 eve 語義 jmm 互動 種類型 java原始碼 https
一 簡介
在並發編程中,兩個線程(A、B)同時操作一個普通變數的時候會出現線程A在操作變數時線程B也將變數操作了,此時線程A是無法感知變數發生變化的,造成變數改變錯誤。更據以上例子我們需要解決的問題就是線程之間的通訊以及同步。表在命令式編程中,線程之間的通訊機制有兩種:共用記憶體和訊息傳遞。Java並發採用的是共用記憶體模型,Java線程之間的通訊總是隱式進行,整個通訊對程式員玩完全透明。
二 Java記憶體模型的抽象結構
Java線程之間的通訊由Java記憶體模型(本文簡稱為JMM)控制,JMM決定一個線程對共用變數的寫入何時對另一個線程可見。從抽象的角度來看,JMM定義了線程和主記憶體之間的抽象關係:線程之間的共用變數儲存在主記憶體(Main Memory)中,每個線程都有一個私人的本地記憶體(Local Memory),本地記憶體中儲存了該線程以讀/寫共用變數的副本。本地記憶體是JMM的一個抽象概念,並不真實存在。它涵蓋了緩衝、寫緩衝區、寄存器以及其他的硬體和編譯器最佳化。Java記憶體模型的抽象如下。
從來看,線程A與線程B之間如要通訊的話,必須要經曆下面2個步驟:
1.首先,線程A把本地記憶體A中更新過的共用變數重新整理到主記憶體中去。
2.然後,線程B到主記憶體中去讀取線程A之前已更新過的共用變數。
下面通過來說明這兩個步驟:
如所示,本地記憶體A和B有主記憶體中共用變數x的副本。假設初始時,這三個記憶體中的x值都為0。線程A在執行時,把更新後的x值(假設值為1)臨時存放在自己的本地記憶體A中。當線程A和線程B需要通訊時,線程A首先會把自己本地記憶體中修改後的x值重新整理到主記憶體中,此時主記憶體中的x值變為了1。隨後,線程B到主記憶體中去讀取線程A更新後的x值,此時線程B的本地記憶體的x值也變為了1。
從整體來看,這兩個步驟實質上是線程A在向線程B發送訊息,而且這個通訊過程必須要經過主記憶體。JMM通過控制主記憶體與每個線程的本地記憶體之間的互動,來為java程式員提供記憶體可見度保證。
三 重排序
在執行程式時,為了提高效能,編譯器和處理器常常會對指令做重排序。重排序分3種類型。
1. 編譯器最佳化的重排序。編譯器在不改變單線程程式語義的前提下,可以重新安排語句的執行順序。
2. 指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-LevelParallelism,ILP)來將多條指令重疊執行。如果不存在資料依賴性,處理器可以改變語句對應機器指令的執行順序。
3. 記憶體系統的重排序。由於處理器使用緩衝和讀/寫緩衝區,這使得載入和儲存操作看上去可能是在亂序執行
從java原始碼到最終實際執行的指令序列,會分別經曆下面三種重排序:
上述的1屬於編譯器重排序,2和3屬於處理器重排序。這些重排序可能會導致多線程程式出現記憶體可見度問題。對於編譯器,JMM的編譯器重定序會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對於處理器重排序,JMM的處理器重定序會要求Java編譯器在產生指令序列時,插入特定類型的記憶體屏障(Memory Barriers,Intel稱之為Memory Fence)指令,通過記憶體屏障指令來禁止特定類型的處理器重排序。JMM屬於語言級的記憶體模型,它確保在不同的編譯器和不同的處理器平台之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程式員提供一致的記憶體可見度保證。
為了保證記憶體可見度,Java編譯器在產生指令序列的適當位置會插入記憶體屏障指令來禁止特定類型的處理器重排序。JMM把記憶體屏障指令分為4類
四 happens-before
JSR-133提出了happens-before的概念,通過這個概念來闡述操作之間的記憶體可見度。如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在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。
注意:兩個操作之間具有happens-before關係,並不意味著前一個操作必須要在後一個操作之前執行!happens-before僅僅要求前一個操作(執行的結果)對後一個操作可見,且前一個操作按順序排在第二個操作之前
5.start()規則:如果線程A執行操作ThreadB.start()(啟動線程B),那麼A線程ThreadB.start()操作happens-before於線程B中的任意操作。
6.join()規則:如果線程A執行操作ThreadB.join()並成功返回,那麼線程B中的任意操happens-before於線程A從ThreadB.join()操作成功返回
happens-before與JMM的關係如所示:
如所示,一個happens-before規則通常對應於多個編譯器重定序和處理器重定序。對於java程式員來說,happens-before規則簡單易懂,它避免程式員為了理解JMM提供的記憶體可見度保證而去學習複雜的重定序以及這些規則的具體實現
五 說明
只要是記憶體模型的就逃不掉程曉明寫的記憶體深入理解Java記憶體模型,我是看完他的《深入Java記憶體模型》寫的本來想做個總結的結果總結下來發現90%還是書上的。本人能力有限將我認為比較總要的有助於理解Java的記憶體模型的東西寫到這裡面。參考文獻裡面有程曉敏部落格地址
深入理解Java記憶體模型(一)——基礎
並發系列(二)----Java記憶體模型