恐怕比較一下volatile和synchronized的不同是最容易解釋清楚的。volatile是變數修飾符,而synchronized則作用於一段代碼或方法;看如下三句get代碼:
- int i1; int geti1() {return i1;}
- volatile int i2; int geti2() {return i2;}
- int i3; synchronized int geti3() {return i3;}
geti1()得到儲存在當前線程中i1的數值。多個線程有多個i1變數拷貝,而且這些i1之間可以互不相同。換句話說,另一個線程可能已經改變了它線程內的i1值,而這個值可以和當前線程中的i1值不相同。事實上,Java有個思想叫“主”記憶體地區,這裡存放了變數目前的“準確值”。每個線程可以有它自己的變數拷貝,而這個變數拷貝值可以和“主”記憶體地區裡存放的不同。因此實際上存在一種可能:“主”記憶體地區裡的i1值是1,線程1裡的i1值是2,線程2裡的i1值是3——這線上程1和線程2都改變了它們各自的i1值,而且這個改變還沒來得及傳遞給“主”記憶體地區或其他線程時就會發生。
而geti2()得到的是“主”記憶體地區的i2數值。用volatile修飾後的變數不允許有不同於“主”記憶體地區的變數拷貝。換句話說,一個變數經volatile修飾後在所有線程中必須是同步的;任何線程中改變了它的值,所有其他線程立即擷取到了相同的值。理所當然的,volatile修飾的變數存取時比一般變數消耗的資源要多一點,因為線程有它自己的變數拷貝更為高效。
既然volatile關鍵字已經實現了線程間資料同步,又要synchronized幹什麼呢?呵呵,它們之間有兩點不同。首先,synchronized獲得並釋放監視器——如果兩個線程使用了同一個對象鎖,監視器能強制保證代碼塊同時只被一個線程所執行——這是眾所周知的事實。但是,synchronized也同步記憶體:事實上,synchronized在“主”記憶體地區同步整個線程的記憶體。因此,執行geti3()方法做了如下幾步:
1. 線程請求獲得監視this對象的對象鎖(假設未被鎖,否則線程等待直到鎖釋放)
2. 線程記憶體的資料被消除,從“主”記憶體地區中讀入(Java虛擬機器能最佳化此步。。。[後面的不知道怎麼表達,汗])
3. 代碼塊被執行
4. 對於變數的任何改變現在可以安全地寫到“主”記憶體地區中(不過geti3()方法不會改變變數值)
5. 線程釋放監視this對象的對象鎖
因此volatile只是線上程記憶體和“主”記憶體間同步某個變數的值,而synchronized通過鎖定和解鎖某個監視器同步所有變數的值。顯然synchronized要比volatile消耗更多資源。