深入理解Java記憶體模型JMM與volatile關鍵字

Tu9oh0st發表於2019-07-10

深入理解Java記憶體模型JMM與volatile關鍵字

多核併發快取架構

深入理解Java記憶體模型JMM與volatile關鍵字

Java記憶體模型

Java執行緒記憶體模型跟CPU快取模型類似,是基於CPU快取模型來建立的,Java執行緒記憶體模型是標準化的,遮蔽掉了底層不同計算機的區別。

深入理解Java記憶體模型JMM與volatile關鍵字

例子

編寫程式碼來分析

public class VolatileVisibilityTest {
    private static boolean initFlag = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("等待資料準備..");
                while (!initFlag){

                }
                System.out.println("============資料準備完畢,執行程式邏輯");
            }
        }).start();
        Thread.sleep(2000);
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                prepareData();
            }
        }).start();
    }

    public static void prepareData(){
 ![](https://img2018.cnblogs.com/blog/1330447/201907/1330447-20190710190530342-1378616179.png)

       System.out.println("資料準備中..");
        initFlag = true;
        System.out.println("資料準備完畢!");
    }
}

執行程式,列印結果

並未出現

============資料準備完畢,執行程式邏輯

這段結果

分析

第一個執行緒給了initFlag為false,第二個執行了prepareData()所以initFlag為true,但是第一個執行緒中的flag還是為false。

如果給initFlag加個volatile關鍵字:

public class VolatileVisibilityTest {
    private static volatile boolean initFlag = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("等待資料準備..");
                while (!initFlag){

                }
                System.out.println("============資料準備完畢,執行程式邏輯");
            }
        }).start();
        Thread.sleep(2000);

        new Thread(new Runnable() {
            @Override
            public void run() {
                prepareData();
            }
        }).start();
    }
    public static void prepareData(){
        System.out.println("資料準備中..");
        initFlag = true;
        System.out.println("資料準備完畢!");
    }
}

執行程式,返回結果

深入理解Java記憶體模型JMM與volatile關鍵字

JMM資料原子操作

  • read(讀取):從主記憶體讀取資料
  • load(載入):將主記憶體讀取到的資料寫入工作記憶體
  • use(使用):從工作記憶體讀取資料來計算
  • assign(賦值):將計算好的值重新賦值到工作記憶體中
  • strore(儲存):將工作記憶體資料寫入主記憶體
  • write(寫入):將store過去的變數值賦值給主記憶體中的變數
  • lock(鎖定):將主記憶體變數枷鎖,表示為執行緒獨佔狀態
  • unlock(解鎖):將主記憶體變數解鎖,解鎖後其他執行緒可以鎖定該變數

整個過程如下

深入理解Java記憶體模型JMM與volatile關鍵字

JMM快取不一致問題

  • 匯流排枷鎖(效能太低)

    • CPU從主記憶體讀取資料到快取記憶體,會在匯流排對這個資料加鎖,這樣其他CPU沒法去讀或寫這個資料,直到這個CPU使用完整資料釋放鎖之後其他CPU才能讀取該資料。

深入理解Java記憶體模型JMM與volatile關鍵字

  • MESI快取一致性協議

    • 多個CPU從主記憶體讀取同一個資料到各自的快取記憶體,當其中某個CPU修改了快取裡的資料,該資料會馬上同步回主記憶體,其他CPU通過匯流排嗅探機制可以感知到資料的變化從而將自己快取裡的資料失效。

深入理解Java記憶體模型JMM與volatile關鍵字

Volatile可見性底層實現原理

  • Volatile快取可見性實現原理

    • 底層實現主要是通過彙編lock字首指令,它會鎖定這塊記憶體區域的快取並回寫到主記憶體,此操作被稱為“快取鎖定”,MESI快取一致性協議機制會阻止同時修改被兩個以上處理器快取的記憶體區域資料。一個處理器的快取值通過匯流排回寫到記憶體會導致其他處理器響應的快取失效。

    Java程式彙編程式碼檢視

    • -server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*VolatileVisibilityTest.prepareData
    • 需要先下載hsdis-amd64

深入理解Java記憶體模型JMM與volatile關鍵字

IDEA這樣設定

顯式出的結果,其中volatile修飾的彙編程式碼如下:

0x000000000349eaff: lock add dword ptr [rsp],0h ;*putstatic initFlag
; - com.tugohost.concurrent.VolatileVisibilityTest::prepareData@9 (line 31)

可見性、原子性與有序性

  • 併發程式設計三大特性:可見性,原子性,有序性
  • Volatile保證可見性與有序性,但是不保證原子性,保證原子性需要藉助synchronized這樣的鎖機制。

相關文章