面試官:volatile如何保證可見性的,具體如何實現?

JavaBuild發表於2024-03-19

寫在開頭

在之前的幾篇博文中,我們都提到了 volatile 關鍵字,這個單詞中文釋義為:不穩定的,易揮發的,在Java中代表變數修飾符,用來修飾會被不同執行緒訪問和修改的變數,對於方法,程式碼塊,方法引數,區域性變數以及例項常量,類常量多不能進行修飾。

自JDK1.5之後,官網對volatile進行了語義增強,這讓它在Java多執行緒領域越發重要!因此,我們今天就抽一晚上時間,來學一學這個關鍵字,首先,我們從標題入手,思考這樣的一個問題:

volatile如何保證可見性,具體如何實現的?

帶著疑問,我們繼續往下閱讀!

volatile如何保證可見性

volatile保證了不同執行緒對共享變數進行操作時的可見性,即一個執行緒修改了共享變數的值,共享變數修改後的值對其他執行緒立即可見。

我們先透過之前寫的一個小案例來感受一下什麼是可見性問題:

【程式碼示例1】

public class Test {
    //是否停止 變數
    private static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
        //啟動執行緒 1,當 stop 為 true,結束迴圈
        new Thread(() -> {
            System.out.println("執行緒 1 正在執行...");
            while (!stop) ;
            System.out.println("執行緒 1 終止");
        }).start();
        //休眠 1 秒
        Thread.sleep(1000);
        //啟動執行緒 2, 設定 stop = true
        new Thread(() -> {
            System.out.println("執行緒 2 正在執行...");
            stop = true;
            System.out.println("設定 stop 變數為 true.");
        }).start();
    }
}

輸出:

執行緒 1 正在執行...
執行緒 2 正在執行...
設定 stop 變數為 true.

原因:
我們會發現,執行緒1執行起來後,休眠1秒,啟動執行緒2,可即便執行緒2把stop設定為true了,執行緒1仍然沒有停止,這個就是因為 CPU 快取導致的可見性導致的問題。執行緒 2 設定 stop 變數為 true,執行緒 1 在 CPU 1上執行,讀取的 CPU 1 快取中的 stop 變數仍然為 false,執行緒 1 一直在迴圈執行。
image

那這個問題怎麼解決呢?很好解決!我們排volatile上場可以秒搞定,只需要給stop變數加上volatile修飾符即可!

【程式碼示例2】

//給stop變數增加volatile修飾符
private static volatile boolean stop = false;

輸出:

執行緒 1 正在執行...
執行緒 2 正在執行...
設定 stop 變數為 true.
執行緒 1 終止

從結果中看,執行緒1成功的讀取到了執行緒而設定為true的stop變數值,解決了可見性問題。那volatile到底是什麼讓變數在多個執行緒之間保持可見性的呢?請看下圖!
image

如果我們將變數宣告為 volatile ,這就指示 JVM,這個變數是共享且不穩定的,每次使用它都到主存中進行讀取,具體實現可總結為5步。

  • 1️⃣在生成最低成彙編指令時,對volatile修飾的共享變數寫操作增加Lock字首指令,Lock 字首的指令會引起 CPU 快取寫回記憶體;
  • 2️⃣CPU 的快取回寫到記憶體會導致其他 CPU 快取了該記憶體地址的資料無效;
  • 3️⃣volatile 變數透過快取一致性協議保證每個執行緒獲得最新值;
  • 4️⃣快取一致性協議保證每個 CPU 透過嗅探在匯流排上傳播的資料來檢查自己快取的值是不是修改;
  • 5️⃣當 CPU 發現自己快取行對應的記憶體地址被修改,會將當前 CPU 的快取行設定成無效狀態,重新從記憶體中把資料讀到 CPU 快取。

總結

其實volatile關鍵字不僅僅能解決可見性問題,還可以透過禁止編譯器、CPU 指令重排序和部分 happens-before 規則,解決有序性問題,我們放在下一篇聊。

結尾彩蛋

如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!

image

如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!

image

相關文章