可見性問題復現
執行緒1不會停止,因為執行緒2改變了a的值,執行緒1不知道
public class Test {
public static Integer a = 1;
public static void main(String[] args) {
// 執行緒1 根本停不下來
new Thread(() -> {
while (a == 1){
}
}).start();
// 執行緒2 2秒後修改 a 的值
new Thread(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
a = 2;
}).start();
}
}
但是如果這樣寫執行緒1就會停下來,沒搞明白...(至少上面的程式碼證明了可見性的問題)
public class Test {
public static Integer a = 1;
public static void main(String[] args) {
new Thread(() -> {
while (a == 1){
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
System.out.println("此時變數還是1");
}
}).start();
new Thread(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
a = 2;
}).start();
}
}
第一段程式碼中,如果變數使用 volatile 修飾,執行緒1 就會停下來
volatile
可以保證變數的可見性、有序性
可見性
可以理解為,每個執行緒不是讀取自己的工作記憶體,而是直接讀取主記憶體,每次修改都會回寫主記憶體,更深入的原因是快取一致性,官方描述如下(不是很理解,後面再看):
當CPU寫資料時,如果發現操作的變數是共享變數,即在其他CPU中也存在該變數的副本,會發出訊號通知其他CPU將該變數的快取行置為無效狀態,因此當其他CPU需要讀取這個變數時,發現自己快取中快取該變數的快取行是無效的,那麼它就會從記憶體重新讀取
有序性
java 程式碼最終翻譯成可執行的機器指令會被最佳化、或者只能保證賦值操作啥的是準確的,但是不一定就是我們寫的程式碼的順序,這就是 指令重排序,底層使用記憶體屏障來完成的,對於 volatile 完整的情況應該是這樣的(不是很理解,後面再看)
- 在每一個volatile的寫(store)之前,加入一個StoreStore屏障和一個LoadStore屏障
- 在每一個volatile的寫(store)之後,加入一個StoreLoad屏障和一個StroeStore屏障
- 在每一個volatile的讀(load)之後,加一個LoadLoad屏障和LoadStrore屏障