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

隨着多核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操作。

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