深入理解Java內存模型JMM與volatile關鍵字

深入理解Java內存模型JMM與volatile關鍵字

多核併發緩存架構

Java內存模型

Java線程內存模型跟CPU緩存模型類似,是基於CPU緩存模型來建立的,Java線程內存模型是標準化的,屏蔽掉了底層不同計算機的區別。

例子

編寫代碼來分析

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("數據準備完畢!");
    }
}

執行程序,返回結果

JMM數據原子操作

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

整個過程如下

JMM緩存不一致問題

  • 總線枷鎖(性能太低)

    • CPU從主內存讀取數據到高速緩存,會在總線對這個數據加鎖,這樣其他CPU沒法去讀或寫這個數據,直到這個CPU使用完整數據釋放鎖之後其他CPU才能讀取該數據。

  • MESI緩存一致性協議

    • 多個CPU從主內存讀取同一個數據到各自的高速緩存,當其中某個CPU修改了緩存裏的數據,該數據會馬上同步回主內存,其他CPU通過總線嗅探機制可以感知到數據的變化從而將自己緩存裏的數據失效。

Volatile可見性底層實現原理

  • Volatile緩存可見性實現原理

    • 底層實現主要是通過彙編lock前綴指令,它會鎖定這塊內存區域的緩存並回寫到主內存,此操作被稱爲“緩存鎖定”,MESI緩存一致性協議機制會阻止同時修改被兩個以上處理器緩存的內存區域數據。一個處理器的緩存值通過總線回寫到內存會導致其他處理器響應的緩存失效。

    Java程序彙編代碼查看

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

IDEA這樣設置

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

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

可見性、原子性與有序性

  • 併發編程三大特性:可見性,原子性,有序性
  • Volatile保證可見性與有序性,但是不保證原子性,保證原子性需要藉助synchronized這樣的鎖機制。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章