第三章 Java記憶體模型之volatile⑥
接上一章 ofollow,noindex">Java記憶體模型之順序一致性 ,我們來了解下volatile。
理解volatile特性,一個好方法就是把volatile變數的單個讀、寫 ,可以看成是使用同一個鎖對這些單個讀/寫進行了同步。
class demo{ volatile long v1 = 0L; public void set(long l){ v1 = l; } public long get(){ return v1; } }
class demo{ long v1 = 0L; public synchronized void set(long l){ v1 = l; } public synchronized long get(){ return v1; } }
上面的兩段程式碼效果是一樣的。
我們都知道鎖的 happens-before 規則來保證釋放鎖和獲取鎖的兩個執行緒之間的記憶體可見性。所以意味著一個 volatile 變數的讀,總是能看到(任意執行緒)對這個 volatile 變數的最後寫入。
鎖的語義決定了臨近區程式碼的執行具有原子性,即使64位的 long 或者 double 變數,只要是 volatile 變數,那麼該變數的讀/寫就具有原子性。但是多個 volatile 操作或者 volatile++ 是不具有原子性的。
volatile 具有以下特性:
1)可見性。 對一個 volatile 變數的讀,總是能看到(任意執行緒)對這個 volatile 變數的最後寫入。
2)原子性。對任意的那個 volatile 變數的讀/寫都具有原子性。但是類似於 volatile++ 不具備。
對於程式員來說,volatile 對執行緒的記憶體可見性影響比 volatile 自身的特性更重要。volatile 的讀/寫可以實現執行緒之間的通訊。
對比鎖的釋放-獲得對記憶體的影響,volatile 也具有相同的記憶體語義。volatile 讀==鎖的獲取。volitle 寫 == 鎖的釋放。
當寫一個 volatile 變數時,JMM 會把該執行緒對應的本地記憶體中的共享變數值重新整理到主記憶體中。
當讀取一個 volatile 時,JMM 會把該執行緒對應的本地記憶體置為無效,然後從主記憶體中讀取共享變數。
總結一下 volatile 的讀/寫:
1)執行緒 A 寫一個 volatile 變數,實際上是執行緒 A 向接下來將要讀這個 volatile 變數的某個執行緒發出來(其對共享變數所做的修改的)訊息。
2)執行緒 B 讀一個 volatile 變數,實際上是執行緒 B 接收了之前某個執行緒發出的(在寫這個 volatile 變數之前對這個共享變數修改的)訊息。
3)程式 A 寫一個 volatile 變數,隨後程式 B 讀這個 volatile 變數,這個過程實質上就是執行緒 A 通過主記憶體向執行緒 B 傳送訊息。
其實也印證了我們之前所說的共享記憶體的通訊是隱性的。
為了實現 volatile ,JMM限制了編譯器和處理器的重排序。

重排序規則
總結一下這個規則:
1、當第二個操作是 volatile 寫的時候,不管第一個操作是啥,都不允許重排序。確保 volatile 寫之前的操作不會被編譯器重排序到 volatile 寫之後。
2、當第一個操作是 volatile 讀的時候,不管第二個操作是啥,都不允許重排序。確保 volatile 讀之後的操作不會被編譯器重排序到 volatile 讀之前。
3、當都是兩個都是 volatile 操作的時候,不允許重排序。
所以總結順口溜: 1讀2寫3全部,全都不許重排序。