volatile和synchronized的區別與聯絡[轉]

xusir發表於2014-09-24

volatile是一個變數修飾符,而synchronized是一個方法或塊的修飾符。所以我們使用這兩種關鍵字來指定三種簡單的存取變數的方式。

         int i1;                       int geti1() {return i1;}

volatile int i2;                       int geti2() {return i2;}

     int i3;          synchronized int geti3() {return i3;}

geti1()在當前執行緒中立即獲取在i1變數中的值。執行緒可以獲得變數的本地拷貝,而所獲得的變數的值並不一定與其他執行緒所獲得的值相同。特別是,如果其他的執行緒修改了i1的值,那麼當前執行緒獲得的i1的值可能與修改後的值有所差別。實際上,Java有一種主記憶體的機制,使用一個主記憶體來儲存變數當前的正確的值。執行緒將變數的值拷貝到自己獨立的記憶體中,而這些執行緒的記憶體拷貝可能與主記憶體中的值不同。所以實際當中可能發生這樣的情況,在主記憶體中i1的值為1,執行緒1和執行緒2都更改了i1,但是卻沒把更新的值傳回給主記憶體或其他執行緒中,那麼可能線上程1中i1的值為2,執行緒2中i1的值卻為3。

另一方面,geti2()可以有效的從主記憶體中獲取i2的值。一個volatile型別的變數不允許執行緒從主記憶體中將變數的值拷貝到自己的儲存空間。因此,一個宣告為volatile型別的變數將在所有的執行緒中同步的獲得資料,不論你在任何執行緒中更改了變數,其他的執行緒將立即得到同樣的結果。由於執行緒存取或更改自己的資料拷貝有更高的效率,所以volatile型別變數在效能上有所消耗。

那麼如果volatile變數已經可以使資料線上程間同步,那麼synchronizes用來幹什麼呢?兩者有兩方面的不同。首先,synchronized獲取和釋放由監聽器控制的鎖,如果兩個執行緒都使用一個監聽器(即相同物件鎖),那麼監聽器可以強制在一個時刻只有一個執行緒能處理程式碼塊,這是最一般的同步。另外,synchronized還能使記憶體同步。在實際當中,synchronized使得所有的執行緒記憶體與主記憶體相同步。所以geti3()的執行過程如下:

1.   執行緒從監聽器獲取物件的鎖。(這裡假設監聽器非鎖,否則執行緒只有等到監聽器解鎖才能獲取物件鎖)

2.   執行緒記憶體更新所有的變數,也就是說他將讀取主記憶體中的變數使自己的變數保證有效。(JVM會使用一個“髒”標誌來最優化過程,使得僅僅具有“髒”標誌變數被更新。詳細的情況查詢JAVA規範的17.9)

3.   程式碼塊被執行(在這個例子中,設定返回值為剛剛從主記憶體重置的i3當前的值。)

4.   任何變數的變更將被寫回到主記憶體中。但是這個例子中geti3()沒有什麼變化。

5.   執行緒釋放物件的鎖給監聽器。

所以volatile只能線上程記憶體和主記憶體之間同步一個變數的值,而synchronized則同步線上程記憶體和主記憶體之間的所有變數的值,並且通過鎖住和釋放監聽器來實現。顯然,synchronized在效能上將比volatile更加有所消耗。

 

=============關於兩者的區別===================

1.volatile本質是在告訴jvm當前變數在暫存器(工作記憶體)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變數,只有當前執行緒可以訪問該變數,其他執行緒被阻塞住。
2.volatile僅能使用在變數級別;synchronized則可以使用在變數、方法、和類級別的
3.volatile僅能實現變數的修改可見性,不能保證原子性;而synchronized則可以保證變數的修改可見性和原子性
4.volatile不會造成執行緒的阻塞;synchronized可能會造成執行緒的阻塞。
5.volatile標記的變數不會被編譯器優化;synchronized標記的變數可以被編譯器優化

 

紅字型部分的原因如下:
執行緒A修改了變數還沒結束時,另外的執行緒B可以看到已修改的值,而且可以修改這個變數,而不用等待A釋放鎖,因為Volatile 變數沒上鎖

 

相關文章