理解 JAVA 中的 volatile
一、 volatile簡述
volatile
是Java虛擬機提供的輕量級的同步機制(相對於synchronized
)。主要作用是,1)保證共享變量的可見性;2)禁止指令重排序。
保證可見性
可見性就是指當一個線程修改了共享變量的值時,其他線程能夠立即得知這個修改。。Java內存模型是通過在變量修改后將新值同步回主內 存,在變量讀取前從主內存刷新變量值這種依賴主內存作為傳遞媒介的方式來實現可見性的,無論是 普通變量還是volatile
變量都是如此。普通變量與volatile
變量的區別是,volatile
的特殊規則保證了新值 能立即同步到主內存,以及每次使用前立即從主內存刷新。因此我們可以說volatile
保證了多線程操作時變量的可見性,而普通變量則不能保證這一點。
禁止指令重排
普通的變量僅會保證在該方法的執行過程中所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證變量賦值操作的順序與程序代碼中的執行順序一致。因為在同一個線程的方法執行過程中無法感知到這點。這就是Java內存模型中描述的所謂“線程內表現為串行的語義”(Within-Thread As-If-Serial Semantics)。
摘自《深入理解Java虛擬機》
int f = 1;
int s = 0;
f++;
s++;
上面這段代碼,由於指令重排序的優化,可能實際執行順序與操作順序不一致。下面兩段代碼的雖然順序雖然不一樣,但是語義是相同的。
int f = 1;
f++;
int s = 0;
s++;
int s = 0;
s++;
int f = 1;
f++;
如果使用volatile
修改其中一個變量后,就能避免指令重排。簡單來說,就是代碼在實際執行過程中,並不全是按照編寫的順序進行執行的,在保證單線程執行結果不變的情況下,編譯器或者CPU可能會對指令進行重排序,以提高程序的執行效率。但是在多線程的情況下,指令重排序可能會造成一些問題,最常見的就是雙重校驗鎖單例模式。如果沒有使用volatile關鍵字,則可能會出現其他線程獲取了一個未初始化完成的singleton對象。
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
二、volatile實現原理
1)可見性實現原理
對於volatile關鍵字修飾的變量,當對volatile變量進行寫操作的時候,JVM會向處理器發送一條lock前綴的指令,將這個緩存中的變量回寫到系統主存中。但是就算寫回到內存,如果其他處理器緩存的值還是舊的,再執行計算操作就會有問題,所以在多處理器下,為了保證各個處理器的緩存是一致的,就會實現緩存一致性協議。
緩存一致性協議:每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進行修改操作的時候,會強制重新從系統內存里把數據讀到處理器緩存里。
所以,如果一個變量被volatile所修飾的話,在每次數據變化之后,其值都會被強制刷入主存。而其他處理器的緩存由於遵守了緩存一致性協議,也會把這個變量的值從主存加載到自己的緩存中。這就保證了一個volatile在並發編程中,其值在多個緩存中是可見的。
2)防止指令重排序實現原理
volatile防止指令重排序是通過內存屏障來實現的。內存屏障分為如下三種:
Store Barrier
Store屏障,是x86的”sfence“指令,強制所有在store屏障指令之前的store指令,都在該store屏障指令執行之前被執行。
Load Barrier
Load屏障,是x86上的”ifence“指令,強制所有在load屏障指令之后的load指令,都在該load屏障指令執行之后被執行
Full Barrie
Full屏障,是x86上的”mfence“指令,復合了load和save屏障的功能。
Java內存模型中volatile變量在寫操作之后會插入一個store屏障,在讀操作之前會插入一個load屏障。一個類的final字段會在初始化后插入一個store屏障,來確保final字段在構造函數初始化完成並可被使用時可見。也正是JMM在volatile變量讀寫前后都插入了內存屏障指令,進而保證了指令的順序執行。
參考:
- https://cloud.tencent.com/developer/article/1500256?from=article.detail.1894413
- 《深入理解Java虛擬機:JVM高級特性與最佳實踐》