前篇部落格【死磕Java並發】—–深入分析volatile的實作原理中已經闡述了volatile的特性了:
volatile可見性;對一個volatile的讀,總是可以看到這個變數最終的寫;
volatile原子性;volatile對單一讀/寫具有原子性(32位元Long、Double),但是複合操作除外,例如i++;
JVM底層採用「記憶體屏障」來實現volatile語意
下面LZ就透過happens-before原則和volatile的記憶體語意兩個方向介紹volatile。
在這篇部落格【死磕Java並發】—–Java記憶體模型之happend-before中LZ闡述了happens-before是用來判斷是否存數據競爭、執行緒是否安全的主要依據,它保證了多執行緒環境下的可見性。下面我們就那個經典的例子來分析volatile變數的讀寫建立的happens-before關係。
public class VolatileTest { int i = 0; volatile boolean flag = false; //Thread A public void write(){ i = 2; //1 flag = true; //2 } //Thread B public void read(){ if(flag){ //3 System.out.println("---i = " + i); //4 } } }
依據happens-before原則,就上面程序得到如下關係:
當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數值立即刷新到主記憶體中。所以volatile的寫記憶體語意是直接刷新到主記憶體中,讀的記憶體語意是直接從主記憶體中讀取。當讀取一個volatile變數時,JMM會把該執行緒對應的本地記憶體設定為無效,直接從主記憶體讀取共享變數
那麼volatile的記憶體語意是如何實現的呢?對於一般的變數則會被重新排序,而對於volatile則不能,這樣會影響其記憶體語義,所以為了實作volatile的記憶體語意JMM會限制重排序。其重排序規則如下:
public class VolatileTest { int i = 0; volatile boolean flag = false; public void write(){ i = 2; flag = true; } public void read(){ if(flag){ System.out.println("---i = " + i); } } }上面透過一個例子稍微示範了volatile指令的記憶體屏障圖例。 volatile的記憶體屏障插入策略非常保守,其實在實際中,只要不改變volatile寫-讀得記憶體語義,編譯器可以根據具體情況優化,省略不必要的屏障。如下(摘自方騰飛《Java並發程式設計的藝術》):
public class VolatileBarrierExample { int a = 0; volatile int v1 = 1; volatile int v2 = 2; void readAndWrite(){ int i = v1; //volatile读 int j = v2; //volatile读 a = i + j; //普通读 v1 = i + 1; //volatile写 v2 = j * 2; //volatile写 } }沒有優化的範例圖如下: 我們來分析上圖有哪些記憶體屏障指令是多餘的
1:這個肯定要保留了
2:禁止下面所有的普通寫與上面的volatile讀重排序,但是由於存在第二個volatile讀,那個普通的讀根本無法越過第二個volatile讀。所以可以省略。
3:下面已經不存在普通讀了,可以省略。
4:保留
5:保留
6:下面跟著一個volatile寫,所以可以省略
7:保留
8:保留
所以2、3、6可以省略,其示意圖如下:
以上就是【死磕Java並發】-----Java記憶體模型之分析volatile的內容,更多相關內容請關注PHP中文網(www.php.cn)!