Java多線程基礎:Volatile關鍵字
Volatile關鍵字
Volatile關鍵字主要是使變量在多個線程間可見。
線程的私有堆棧
Java內存模型告訴我們,各個線程會將共享變量從主內存中拷貝到工作內存,然后執行引擎會基於工作內存中的數據進行操作處理 。
這個時機對普通變量是沒有規定的,而針對volatile修飾的變量給java虛擬機特殊的約定,線程對volatile變量的修改會立刻被其他線程所感知,即不會出現數據臟讀的現象,從而保證數據的“可見性”。
實現原理
在生成匯編代碼時會在volatile修飾的共享變量進行寫操作的時候會多出Lock前綴的指令。主要有這兩個方面的影響:
- 將當前處理器緩存行的數據寫回系統內存,即主內存;
- 這個寫回內存的操作會使得其他CPU里緩存了該內存地址的數據無效。
為了提高處理速度,處理器不直接和內存進行通信,而是先將系統內存的數據讀到內部緩存(L1,L2或其他)后再進行操作,但操作完不知道何時會寫到內存。如果對聲明了volatile的變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存。但是,就算寫回到內存,如果其他處理器緩存的值還是舊的,再執行計算操作就會有問題。所以,在多處理器下,為了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器對這個數據進行修改操作的時候,會重新從系統內存中把數據讀到處理器緩存里。因此,經過分析我們可以得出如下結論:
- Lock前綴的指令會引起處理器緩存寫回內存;
- 一個處理器的緩存回寫到內存會導致其他處理器的緩存失效;
- 當處理器發現本地緩存失效后,就會從內存中重讀該變量數據,即可以獲取當前最新值。
這樣針對volatile變量通過這樣的機制就使得每個線程都能獲得該變量的最新值。
無法保證原子性
Volatile關鍵字雖然增加了實例變量在多個線程之間的可見性,但是他卻不具備同步性,也就不具備原子性。
自增操作演示
看一下下面代碼:
public class VolatileExample {
private static volatile int counter = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++)
counter++;
}
});
thread.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter);
}
}
說明:自增操作分解為下面三步1.讀取變量counter的值;2.對counter加一;3.將新值賦值給變量counter。
Volatile使用場景
加鎖機制既可以確保可見性又可以確保原子性,而volatile變量只可以確保可見性。
當且僅當滿足以下所有條件的時,才應該使用volatile變量:
- 對變量的寫入操作不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。
- 該變量不會與其他狀態變量一起納入不變性條件中
- 在訪問變量時不需要加鎖
狀態標記
volatile boolean inited = false;
//線程1:
context = loadContext();
inited = true;
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
參考資料