當我們在談論JMM(Java memory model)的時候,我們在談論些什麼

來源:互聯網
上載者:User

標籤:ogr   可見   zed   加鎖   決定   同步   詳細   code   軌跡   

  前面幾篇中,我們談論了synchronized、final以及voilate的用法和底層實現,都繞不開一個話題-Java記憶體模型(java memory model,簡稱JMM)。Java記憶體模型是保證安全執行緒的基礎,主要描述了程式中全序的同步動作在不同線程訪問共用全域變數時所體現的原子性、可見度和有序性上的限制。

1、定義

  維基百科定義:The Java memory model describes how threads in the Java programming language interact through memory. Together with the description of single-threaded execution of code, the memory model provides the semantics of the Java programming language

  大意是說:Java記憶體模型描述了多個Java線程如何與記憶體互動,同單線程執行一樣,在多線程情境下記憶體模型提供了一個合理正確Java編程語意。

  JSR133規範由JSR133專家組開發,並首次在Java5.0中實現。規範詳細準確的描述了多線程和記憶體互動語意,成為Java規範的一部分,改進原有Java語意中錯誤,模淩兩可的部分,保證Java跨平台性。

  JSR133中關於記憶體模型的定義如下:

  給定一個程式和該程式的一串執行軌跡,記憶體模型描述了該執行軌跡是否是該程式 的一次合法執行。對於Java,記憶體模型檢查執行軌跡中的每次讀操作,然後根據特定規則,檢驗該讀操作觀察到的寫是否合法。記憶體模型描述了某個程式的可能行為。JVM實現可以自由地產生想要的代碼,只 要該程式所有最終執行產生的結果能通過記憶體模型進行預測。這為大量的代碼轉換 提供了充分的自由,包括動作(action)的重排序以及非必要的同步移除。

  記憶體模型的一個進階、非正式的概述顯示其是一組規則,規定了一個線程的寫操作何時會對另一個線程可見。通俗地說,讀操作r通常能看到任何寫操作w寫入的值,意味著w不是在r之後發生,且w看起來沒有被另一個寫操作w‘覆蓋掉(從 r 的角度看)。

  在本記憶體模型規範中使用“讀取(read)”這個詞時,僅是指讀取欄位或數組元素的動作(action)。其它操作的語義,如讀取數組的長度,執行受檢轉換以及虛方法 調用,都不會被資料爭用直接影響到。JVM 實現有責任確保資料爭用不會導致諸如返回錯誤的數組長度或調用虛方法導致段錯誤這類不正確的行為。

  記憶體語義決定著程式中每個時刻能讀到的值。每個單個線程中的動作(action)必須表現為被該線程的語義所控制,不包括讀操作看到的值由記憶體模型決定的情況。當指的是這種情景時,我們說該程式遵守線程內(intra-thread)語義。

2、JMM近似模型

  為了方便理解JMM,相比正式模型精確公式化的定義,JSR133提出了一種近似的模型-Happen-Before記憶體模型,這個描述是正式定義的必要非充分條件。首先需要解釋一下同步動作的定義,同步動作指的是鎖的加鎖解鎖,voilate對象讀取,線程動作以及探測線程是否結束等。與同步動作對應的是同步邊緣(synchronize-with邊緣),同步邊緣可以理解為同步動作間不可交疊屏障,包括以下幾點:

  1. 某個管程m上的解鎖動作synchronizes-with(同步與)所有後續在m上的鎖定動作 (這裡的後續是根據同步順序定義的)。
  2. 對volatile變數v的寫操作synchronizes-with所有後續任意線程對v的讀操 作(這裡的後續是根據同步順序定義的)。
  3. 用於啟動一個線程的動作synchronizes-with該新啟動線程中的第一個動作。
  4. 線程T1的最後一個動作synchronizes-with線程T2中任一用於探測T1是否 終止的動作。T2可能通過調用 T1.isAlive()或者在T1上執行一個join動作來達到這個目的。
  5. 如果線程T1中斷了線程T2,T1的中斷操作synchronizes-with任意時刻任何其它線程(包括T2)用於確定T2是否被中斷的操作。這可以通過拋出一個 InterruptedException或調用Thread.interrupted與Thread.isInterrupted來實現。
  6. 為每個變數寫預設值(0,false或null)的動作synchronizes-with每個線程中的第一個動作。
  7. 雖然在對象分配之前就為該對象中的變數寫入預設值看起來有些奇怪,從概念上看,程式啟動建立對象時都帶有預設的初始值。因此,任何對象的預設初始化操作 happens-before程式中的任意其它動作(除了寫預設值的操作)。
  8. 調用對象的終結方法時,會隱式的讀取該對象的引用。從一個對象的構造器末尾到該引用的讀取之間存在一個happens-before邊緣。注意,該對象的所有凍結操作happen-before前面那個happens-before邊緣的起始點。

  下面我們詳細說一下這幾條限制,第一條說明了管程(synchronized底層實現monitor)的加鎖解鎖具有同步關係,包括兩點,同一個線程可以對已加鎖對管程重複加鎖,保證解鎖次數一樣即可,對於已經被加鎖管程,線程阻塞並掛起。第二條volatile變數的寫操作會在之後其他線程對volatile變數的讀操作有即時的體現,一般通過記憶體一致性協議實現,一般寫操作後會將對象的緩衝寫入記憶體,並使得其他核的緩衝失效,從而讀取記憶體中最新修改的值,這兩條的實現在前面的文章中已有較詳細的說明。第三條和第四條都是描述線程的啟動線上程的第一個動作之前,線程最後一個動作線上程(探詢終止???這個不是很理解)終止之前,不得重排序。第五條描述的是線程A對線程B的中斷操作同步與線程B的中斷檢測動作,兩個動作互斥發生。第六條第七條說明了,變數的預設值線上程中第一個動作或者其他動作之前,其實有時候編譯器會最佳化變數預設值但賦值,只要該變數還未使用。第八條說明了對象的欄位在構造器中的初始化賦值和對象引用返回,final欄位保證初始化賦值在對象引用返回之前,而非final欄位不保證。

  所有的同步動作構成的全域順序,稱之為同步順序,而synchronize-with邊緣與程式順序構成了Happen-before順序,也就是Happen-before記憶體模型。Java記憶體模型是Happen-before記憶體模型的子集,因為Happen-before模型很多時候違背了因果關係,最為致命的弱點是“值的憑空出現”。關於Java記憶體模型的正式規範請參考JSR133第7章內容(我也看的雲裡霧裡--!)

3、總結

  總的來說,記憶體模型這個概念是由Java語言首先提出,在保證java語義完整性健壯性上起到了很大作用,C/C++以及其他語言在鎖多線程並行存取模型上也參考這樣的記憶體模型。理解JMM能讓我們更加深刻的正確理解JAVA多線程並發執行中與記憶體互動的邏輯,從而編寫出健壯高效的並發程式。

  

 

當我們在談論JMM(Java memory model)的時候,我們在談論些什麼

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.