volatile

CyrusHuang發表於2024-08-30

可見性問題復現

執行緒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 完整的情況應該是這樣的(不是很理解,後面再看)

  1. 在每一個volatile的寫(store)之前,加入一個StoreStore屏障和一個LoadStore屏障
  2. 在每一個volatile的寫(store)之後,加入一個StoreLoad屏障和一個StroeStore屏障
  3. 在每一個volatile的讀(load)之後,加一個LoadLoad屏障和LoadStrore屏障

相關文章