總結:對Java內存模型JMM的理解

JMM規定了線程的工作內存和主內存的交互關係,以及線程之間的可見性和程序的執行順序。一方面,要爲程序員提供足夠強的內存可見性保證;另一方面,對編譯器和處理器的限制要儘可能地放鬆。JMM對程序員屏蔽了CPU以及OS內存的使用問題,能夠使程序在不同的CPU和OS內存上都能夠達到預期的效果。

在jsr-133中是這麼定義的

A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program. For the Java programming language, the memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.

也就是說一個內存模型描述了一個給定的程序和和它的執行路徑是否一個合法的執行路徑。對於java序言來說,內存模型通過考察在程序執行路徑中每一個讀操作,根據特定的規則,檢查寫操作對應的讀操作是否能是有效的。

個人的理解就是:Java 的編譯器和CPU都可以對程序進行優化重排序,但是要遵循一些規則,不能隨意的重排序,例如 volatile,final,以及在釋放和獲取鎖時程序執行的順序,爲多線程打下了基礎。同事還規定了

JMM中的模型規則

兩個比較重要的模型:
Sequential Consistency Memory Model:順序一致性模型。如果程序是正確同步的,程序的執行將具有順序一致性(Sequentially Consistent)——即程序的執行結果與該程序在順序一致性內存模型中的執行結果相同。
第一:一個線程中的所有操作必須按照程序的順序來執行;
第二:(不管程序是否同步)所有線程都只能看到一個單一的操作執行順序。在順序一致性內
存模型中,每個操作都必須原子執行且立刻對所有線程可見

這裏寫圖片描述

正真做到上面提到的順序一致,每一個線程操作的結果在內存中立即對另一個線程可見,會導致程序運行性能的下降,例如,T1線程的每一次寫操作都要寫回內存,要確保對T2線程可見,程序的執行效率相當低。所以JMM並沒有嚴格要求程序滿足該模型,只是能夠確保在程序正確只用了鎖的情況下,能夠確保程序在宏觀上是順序一致的。例如,加鎖和解鎖是順序一致的。

Happens-Before Memory Model : 先行發生模型。happens-before是JMM最核心的概念。

int a = 0;(1)
int b = 0;(2)
int c = a+b;(3)

我們說,1 happen brfore 2,2 happen before 3,根據傳遞性規則,1happen before3。

1)如果一個操作happens-before另一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
2)兩個操作之間存在happens-before關係,並不意味着Java平臺的具體實現必須要按照happens-before關係指定的順序來執行。如果重排序之後的執行結果,與按happens-before關係來執行的結果一致,那麼這種重排序並不非法(也就是說,JMM允許這種重排序)。

第一個規則是爲程序員提供的保證,在同一個線程內,前面的操作結果對後面的程序可見。
第二個規則是向CPU提出的要求,在保證執行結果相同的情況下,可以做程序的重排序,來優化程序的運行。

《JSR-133:Java Memory Model and Thread Specification》定義瞭如下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()操作成功返回。

1),2),4),5),6)都比較容易理解,3)準備在寫一篇blog來說明。

JMM規定的主存和工作內存的交互

JMM規定了下面8種操作在虛擬機實現時保證每一種操作都是原子的不可分的。(long和double 個別操作除外),lock、unlock、read、load、use、assign、store、write。

  • lock(鎖定):作用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。
  • unlock(解鎖):作用於主內存變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。
  • read(讀取):作用於主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用
  • load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
  • use(使用):作用於工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。
  • assign(賦值):作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
  • store(存儲):作用於工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨後的write的操作。
  • write(寫入):作用於主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中。

Java內存模型還規定了在執行上述八種基本操作時,必須滿足如下規則:

  • 不允許read和load、store和write操作之一單獨出現
  • 不允許一個線程丟棄它的最近assign的操作,即變量在工作內存中改變了之後必須同步到主內存中。
  • 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中。
  • 一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執行過了assign和load操作。
  • 一個變量在同一時刻只允許一條線程對其進行lock操作,lock和unlock必須成對出現
  • 如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行load或assign操作初始化變量的值
  • 如果一個變量事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。
  • 對一個變量執行unlock操作之前,必須先把此變量同步到主內存中(執行store和write操作)。

這8種內存訪問操作很繁瑣,後文會使用一個等效判斷原則,即先行發生(happens-before)原則來確定一個內存訪問在併發環境下是否安全。

總之,JMM規定了程序員怎麼和JVM的內存打交道,JVM的內存怎麼和OS內存打交道,主內存怎麼和工作內存打交道。並規定了一些確保程序正確高效運行的規則。

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