1. 程式人生 > >基於Java記憶體模型:Synchronized和Volatile的比較

基於Java記憶體模型:Synchronized和Volatile的比較

1.Java記憶體模型

 

 

1.Java記憶體模型

1.Java記憶體模型

   1)  Java虛擬機器規範試圖定義一種Java記憶體模型,來遮蔽掉各種硬體和作業系統的記憶體訪問差異,以實現讓Java程式在各種平臺下都能達到一致的記憶體訪問效果。

   2)Java記憶體模型的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體中和從記憶體中取出變數這樣得底層細節。

  3)Java記憶體模型規定了所有的變數都儲存在主記憶體中

  4)每條執行緒都有自己的工作記憶體,執行緒的工作記憶體儲存了該執行緒使用到的變數的主記憶體副本拷貝,執行緒對變數的所有操作都必須在工作記憶體中進行,而不能直接讀寫主記憶體的變數。

 

 

Java記憶體模型

Java記憶體模型

2.記憶體間互動操作

Java記憶體模型定義了以下8種原子操作,來實現一個變數從主記憶體拷貝到工作記憶體以及從工作記憶體同步回主記憶體的實現細節。

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

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

read:作用於主記憶體的變數,它把一個變數的值從主記憶體傳輸到執行緒的工作記憶體,以便隨後的load操作。

load:作用於工作記憶體的變數,它把read操作的得到的變數放入工作記憶體的變數副本中。

use:作用於工作記憶體的變數,它把工作記憶體中一個變數的值傳遞給執行引擎,每當虛擬機器遇到一個需要用到變數的值得位元組碼指令時執行這個操作。

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

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

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

 

 

3.原子性,可見性,有序性

3.原子性,可見性,有序性

 原子性:由Java記憶體模型來直接保證的原子性變數操作包括read,load,assign,use,store和write,基本型別訪問讀寫是具備原子性的。通俗講,就是不可以再分為多步操作了,它是最小操作單元。例如:a=2;具有原子性,但是i++;不具有原子性。它需要這樣三個步驟:1.取出i  2:計算i+1 3.計算結果寫入記憶體。

可見性:可見是指當一個執行緒修改了共享變數的值,其他執行緒能夠立即得知這個修改。Java記憶體模型是通過在變數修改後將新值同步回主記憶體,在變數讀取前從主記憶體重新整理變數值這種依賴主記憶體作為傳遞媒介的方式來實現可見性的。

有序性:如果在本執行緒內觀察,所有操作都是有序的;如果在一個執行緒中觀察另一個執行緒,所以操作都是無序的。前半句是指:執行緒內表現為序列,後邊句是指:指令重排序以及工作記憶體主記憶體同步延遲現象。

4.volatile:保證可見性,禁止指令重排序優化,但不保證原子性。

 1)當一個變數定義為volatile時,它保證了此變數對所有執行緒可見。當在讀取volatile變數時,會進行load操作(從主記憶體讀取,放入工作記憶體變數中)。當=對volatile變數執行寫操作時,會在寫入後,進行store操作(把工作記憶體變數更新到主記憶體)。所以volatile具有可見性。

 2)但是,volatile不保證原子性。先來看個demo:

 

 

  

  發起5個執行緒,每個執行緒對inc進行10000次自增操作,若果能夠正確併發的話,應該輸出50000,但是輸出的是47441,並且每次執行,輸出結果都不一樣嗎,幾乎都低於50000.,因為inc++不是原子操作。因此,volatile不能保證原子性。

在符合以下運算場景,才使用volatile變數控制併發:

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

  (2)變數不需要與其他的狀態變數共同參與不變約束

 比如:

 

 

 

 3)volatile變數禁止指令重排序優化。

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

 

 

如果定義initialized變數時沒有volatile修飾,就可能指令重排序的優化,導致A執行緒中‘initialized=true’被提前執行,這樣線上程B中使用配置資訊程式碼就會報錯。

5.synchronized:保證原子性,可見性,有序性(指令重排)

 基於Java記憶體模型, synchronized執行流程:

 (1)執行緒獲得獲得互斥鎖

 (2)清空工作記憶體

 (3)在主記憶體中拷貝最新變數的副本到工作記憶體

 (4)執行完程式碼

 (5)將更改後的共享變數的值重新整理到記憶體

(6)釋放互斥鎖

  synchronized一般有兩種使用方式,同步方法和同步程式碼塊。Java虛擬機器基於進入和退出Monitor物件來實現synchronized程式碼塊同步和方法同步,但二者在位元組碼層面的表現略有差別。

1. synchronized同步塊:JVM採用monitorenter、monitorexit兩個指令來實現同步。

2.synchronized方法:對於同步方法,JVM採用ACC_SYNCHRONIZED標記符來實現同步

 即synchronized通過加鎖保證了原子性,可見性,有序性。再看以下demo:

 

 

  發起5個執行緒,每個執行緒對inc進行10000次自增操作,能夠正確併發的話,每次都輸出50000。