JMM
JVM定義了一種Java內存模型來屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓Java程序在各種平臺下都能達到一致的內存訪問效果.在此之前,C/C++直接使用物理硬件和操作系統的內存模型,因此,會由于不同平臺下的內存模型差異,有可能導致程序在一套平臺上并發完全正常,而在另一套平臺上并發訪問經常出錯.
主內存和工作內存
Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在JVM中將變量存儲到內存和從內存中取出變量這樣的底層細節. 此處的變量包括實例字段,靜態字段和構成數組對象的元素,但不包括局部變量和方法參數,因為后兩者是線程私有的,不會被線程共享.
Java內存模型規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的所有操作(讀取/賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量.不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成.線程,主內存,工作內存三者的交互關系如下所示:
內存間的交互操作
關于主內存與工作內存之間的具體交互協議,即一個變量如何從主內存拷貝到工作內存,如何從工作內存同步回主內存之類的實現細節,Java內存模型中定義了如下8種操作來完成.JVM實現時必須保證下面提及的每一種操作是原子的,不可再分的.
?lock(鎖定):作用于主內存的變量,它把一個變量標識為一條線程獨占的狀態.
unlock(解鎖):作用于主內存的變量,它把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其它線程鎖定.
read(讀取):作用于主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨后load動作使用.
load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中.
use(使用):作用于工作內存中的變量,它把工作內存中一個變量值傳遞給執行引擎.
assign(賦值):作用于工作內存中的變量,它把一個從執行引擎接收到的值賦給工作內存變量.
store(存儲):作用于工作內存中的變量,它把工作內存中一個變量的值傳送到主內存中,以便后續write操作的使用.
write(寫入):作用于主內存中的變量,它把store操作從工作內存中得到變量的值放入主內存的變量中.
?Java內存模型的三大特性:
原子性:由Java內存模型來直接保證的原子性變量操作包括read, load, assign, use, store和read.大致可以認為,基本數據類型的訪問讀寫是具備原子性的.如果需要更大范圍的原子性,需要synchronized關鍵字約束.(即一個操作或者多個操作要么全部執行并且執行的過程不會被任何因素打斷,要么都不執行).
可見性:可見性是指當一個線程修改了共享變量的值,其它線程能夠立即得知這個修改.volatile,synchronized,final三個關鍵字可以實現可見性.
有序性:如果在本線程內觀察,所有的操作都是有序的;如果在線程中觀察另一個線程,所有的操作都是無序的.前半句是指"線程內表現為串行",后半句是指"指令重排序"和"工作內存和主內存同步延遲現象".
Java內存模型具備一些先天的"有序性",即不需要通過任何手段就能夠得到保證的有序性,這個通常也稱為happens-before原則.如果兩個操作的執行次序無法從happens-before原則推導出來,那么它們就不能夠保證它們的有序性,虛擬機可以隨意地對它們進行重排序.
下面就來具體介紹一下happens-before原則(現行發生原則):
程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作現行發生于書寫在后面的操作.
鎖定規則:一個unLock操作先行發生于后面對同一個鎖的lock操作.
volatile變量規則:對一個變量的寫操作先行發生于后面對這個變量的讀操作.
傳遞規則:如果操作A現行發生于操作B,而操作B又現行發生于操作C,則可以得出操作A現行發生于操作C
線程啟動規則:Thread對象的start()方法先行發生于此線程的每一個動作.
線程中斷規則:對線程interrupt()方法的調用先行發生于被中斷線程的代碼.
線程終結規則:線程中所有的操作都先行發生于線程的終止檢測,我們可以通過Thread.join()方法結束,Thread.isAlive()的返回值手段檢測到線程已經終止運行.
對象終結規則:一個對象的初始化完成先行于它的finalize()方法(用于在對象被垃圾回收器回收之前執行一些清理操作)的開始.?
也就是說,想要并發程序正確地執行,必須要保證原子性,可見性以及有序性.只要有一個沒有被保證,就有可能會導致程序運行不正確.?