Java內存模型
Java虛擬機規范中試圖定義一種Java內存模型(Java Memory Model,簡稱JMM)來屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓Java程序在各種平臺下都能達到一致的內存訪問效果。
定義Java內存模型并非一件容易的事情,這個模型必須定義得足夠嚴謹,才能讓Java的并非內存訪問操作不會產生歧義:但是,也必須定義的足夠寬松,使得虛擬機的實現有足夠的自由空間去利用硬件的各種特性(寄存器、高速緩存和指令集中某些特有的指令)來獲取更好的執行速度。經過長時間的驗證和修補,在JDK1.5(實現了JSR-133)發布后,Java內存模型已經成熟和完善起來了。
主內存和工作內存
Java內存模型的主要目標就是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。此處的變量(Variables)與Java編程中所說的變量有所區別,它包括了實例字段、靜態字段和構成數組對象的元素,但不包括局部變量與方法參數,因為后者是線程私有,不會被共享,自然就不會存在競爭問題。
Java內存模型規定了所有的變量都存儲在主內存(Main Memory)中,每個線程還有自己的工作內存(Working Memory),線程的工作內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量,不同線程之間也無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成。線程、主內存、工作內存三者的交互關系如下圖所示:
原子性、可見性與有序性
Java內存模型是圍繞著在并發過程中如何處理原子性、可見性與有序性這3個特征來建立,我們逐個來看一下哪些操作實現了這3個特性。
原子性
原子性是指一個操作或者多個操作 要么全部執行并且執行的過程不會被任何因素打斷,要么就都不執行。
除了long型字段和double型字段外,Java內存模型確保訪問任意類型字段所對應的內存單元都是原子的。這包括引用其它對象的引用類型的字段。此外,volatile long 和volatile double也具有原子性 。
如果應用程序需要一個更大范圍的原子性保證,Java內存模型還提供了顯示鎖和synchronized 關鍵字。
可見性
可見性是指當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。
Java內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存將刷新變量值這種依賴主內存作為傳遞媒介的方式來實現可見性的。無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區別是:volatile的特殊規則保證了新值能立即同步到主內存,已經每次使用前立即從主內存刷新。因此,volatile關鍵字可以保證多線程操作時變量的可見性。
除了volatile之外,Java還有兩個關鍵字能實現可見性,即synchronized和final。
有序性
有序性是指 程序執行的順序按照代碼中的先后順序執行。
Java語言提供了volatile和synchronized兩個關鍵字來保證線程之間操作的有序性。
happens-before
從JDK5開始,java使用新的JSR -133內存模型(本文除非特別說明,針對的都是JSR- 133內存模型)。JSR-133使用happens-before的概念來闡述操作之間的內存可見性。在JMM中,如果一個操作執行的結果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關系。這里提到的兩個操作既可以是在一個線程之內,也可以是在不同線程之間。
與程序員密切相關的happens-before規則如下:
- 程序順序規則:一個線程中的每個操作,happens- before 于該線程中的任意后續操作。
- 監視器鎖規則:對一個監視器鎖的解鎖,happens- before 于隨后對這個監視器鎖的加鎖。
- volatile變量規則:對一個volatile域的寫,happens- before 于任意后續對這個volatile域的讀。
- 傳遞性:如果A happens- before B,且B happens- before C,那么A happens- before C。
注意,兩個操作之間具有happens-before關系,并不意味著前一個操作必須要在后一個操作之前執行!happens-before僅僅要求前一個操作(執行的結果)對后一個操作可見,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)。