深入理解volatile原理與使用
Volatile稱之爲輕量級鎖,被volatile修飾的變量,在線程之間是可見的,保證不了非原子性操作,它比synchronized的使用和執行成本更低,因爲它不會引起線程上下文的切換和調度。
有volatile變量修飾的共享變量在進行寫操作的時候彙編代碼中會出現Lock指令
Lock指令在多核處理器下會引發兩件事情:
- 將當前處理器緩存行的內容寫回到系統內存
- 寫回到內存的操作會使在其他CPU裏緩存了該內存地址的數據失效,Intel64處理器使用MESI(修改,獨佔,共享,無效)控制協議去維護內部緩存和其他處理器緩存的一致性。
爲了提高處理速度,處理器不直接與內存進行通行,而是將系統內存的數據獨到內部緩存(L1,L2或其它)後再進行操作。如果對申明瞭volatile的變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存中。在多處理器下,爲了保證各個處理器中的緩存是一致的,就會實現緩存一致性協議,每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器緩存行設置爲無效狀態,當處理器對這個數據進行修改操作的時候,會重新從系統內存中把數據讀到處理器緩存中。
下面的代碼給出了一個volatile變量常用的場景,在代碼中,設置run變量爲volatile,在第一個線程執行完畢後,將run設置爲true,而在第二個線程中,首先不停地自旋操作,判斷run變量是否爲true,然後在進行線程任務。
public class Demo2 {
public volatile boolean run = false;
public static void main(String[] args) {
Demo2 d = new Demo2();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1;i<=10;i++) {
System.err.println("執行了第 " + i + " 次");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
d.run = true;
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(!d.run) {
// 不執行
}
System.err.println("線程2執行了...");
}
}).start();
}
}