《深入理解java虛擬機器》學習筆記9——併發程式設計(一)

yangxi_001發表於2013-12-04

隨著多核CPU的高速發展,為了充分利用硬體的計算資源,作業系統的併發多工功能正變得越來越重要,但是CPU在進行計算時,還需要從記憶體讀取輸出,並將計算結果存放到記憶體中,然而由於CPU的運算速度比記憶體高几個數量級,CPU內的暫存器數量和容量有限,為了不讓CPU長時間處於等待記憶體的空閒狀態,在CPU和記憶體之間引入了速度接近CPU的快取記憶體Cache作為CPU和記憶體之間的緩衝。計算機硬體併發的原理如下:

Java虛擬機器對併發的支援類似於計算機硬體,java虛擬機器的併發支援是通過java虛擬機器的記憶體模型來實現的。Java虛擬機器的記憶體模型分為主記憶體和工作記憶體,程式中所有的變數都儲存在主記憶體中,每個執行緒有自己的私有工作記憶體,工作記憶體中儲存了被該執行緒使用到的變數的主記憶體拷貝,執行緒對變數的所有操作(讀取、賦值等)都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數,不同執行緒之間也無法直接訪問對方工作記憶體中的變數,執行緒間變數值的傳遞需要通過主記憶體來完成。Java虛擬機器併發原理如下:


Java虛擬機器記憶體模型中定義了8種關於主記憶體和工作記憶體的互動協議操作:

(1).lock鎖定:作用於主記憶體的變數,把一個變數標識為一條執行緒獨佔狀態。

(2).unlock解鎖:作用於主記憶體的變數,把一個處於鎖定狀態的變數釋放出來,釋放後的變數可以被其他執行緒鎖定。

(3).read讀取:作用於主內的變數,把一個變數的值從主記憶體傳輸到執行緒的工作記憶體中,以便隨後的load動作使用。

(4).load載入:作用於工作記憶體的變數,把read讀取操作從主記憶體中得到的變數值放入工作記憶體的變數拷貝中。

(5).use使用:作用於工作記憶體的變數,把工作記憶體中一個變數的值傳遞給java虛擬機器執行引擎,每當虛擬機器遇到一個需要使用到變數值的位元組碼指令時將會執行該操作。

(6).assign賦值:作用於工作記憶體變數,把一個從執行引擎接收到的變數的值賦值給工作變數,每當虛擬機器遇到一個給變數賦值的位元組碼時將會執行該操作。

(7).store儲存:作用於工作記憶體的變數,把工作記憶體中一個變數的值傳送到主記憶體中,以便隨後的write操作使用。

(8).write寫入:作用於主記憶體的變數,把store操作從工作記憶體中得到的變數值放入主記憶體的變數中。

Java記憶體模型對上述8種操作有如下的約束:

(1).把一個變數從主記憶體複製到工作記憶體中必須順序執行read讀入操作和load載入操作。

把一個變數從工作記憶體同步回主記憶體中必須順序執行store儲存操作和write寫入操作。

read和load操作之間、store和write操作之間可以插入其他指令,但是read和load操作、store和write操作必須要按順序執行,即不允許read和load、store和write操作之一單獨出現。

(2).不允許一個執行緒丟棄它的最近的assign賦值操作,即工作記憶體變數值改變之後必須同步回主記憶體。只有發生過assign賦值操作的變數才需要從工作記憶體同步回主記憶體。

(3).一個新變數只能在主記憶體中產生,不允許在工作記憶體中直接使用一個未被初始化(load或assign)的變數,即一個變數在進行use和store操作之前,必須先執行過assgin和load操作。

(4).一個變數在同一時刻只允許一條執行緒對其進行lock鎖定操作,但是lock鎖定可以被一條執行緒重複執行多次,多次執行lock之後,只有執行相同次數的unlock操作變數才會被解鎖。

(5).如果對一個變數執行lock鎖定操作,將會清空工作記憶體中該變數的值,在執行引擎使用這個變數前,需要重新執行load或assign操作初始化變數的值。

(6).如果一個變數事先沒有被lock鎖定,則不允許對這個變數進行unlock解鎖操作,也不允許對一個被別的執行緒鎖定的變數進行unlock解鎖。

(7).一個變數進行unlock解鎖操作之前,必須先把此變數同步回主記憶體中(執行store和write操作)。

Java中的關鍵字volatile是java虛擬機器提供的最輕量級的執行緒同步機制,當一個變數被宣告為volatile之後,該變數將具備以下兩種特性:

(1).volatile保證變數對所有執行緒的可見性,即任何一個執行緒修改了該變數的值之後,新值對於所有其他執行緒都是可以立即得知的。

而普通變數需要先將工作記憶體中的變數同步回主記憶體,其他執行緒都需要從主記憶體重新讀取變數的值才能使用最新修改後的值。

volatile變數也可以在各個工作記憶體中存在不一致的情況,但由於每次使用之前都需要先重新整理(工作記憶體變數重新執行初始化),執行引擎看不到變數不一致的情況,因此可以任務volatile變數不存在不一致的情況。

但是java中的運算並非全部都是原子操作,因此volatile變數的執行在併發下一樣是執行緒不安全的。

由於volatile變數只能保證可見性,只有在符合如下兩條規則情況才是執行緒安全的。

a.運算結果不依賴變數的當前值,或者能夠確保只有單一執行緒修改變數的值。

b.變數不需要與其他其他變數共同參與不變約束。

不符合上述兩條規則情況下,仍然需要通過synchronized同步關鍵字或者加鎖機制來保證執行緒安全。

(2).volatile禁止指令重排序優化。

普通變數僅能保證在方法執行過程中所有依賴賦值結果的地方都能獲取正確的結果,而無法保證變數賦值操作順序與程式程式碼執行順序一致。

volatile禁止指令重排序,因此volatile變數的約束如下:

a.volatile變數的操作必須按read->load->use順序,即每次在工作記憶體中使用變數前必須先從主記憶體中重新整理最新的值,以保證能看到其他執行緒對變數的最新修改。

b. volatile變數的操作必須按assign->store->write順序,即每次在工作記憶體為變數賦值之後必須將變數的值同步回主記憶體,以保證讓其他執行緒能看到變數的最新修改。

c.若執行緒對volatile變數A的assign或者use操作先於對volatile變數B的assign或者use操作,則執行緒對volatile變數A的read/load或者store/write操作也必定先於對volatile變數B的read/load或者store/write操作。

相關文章