Java內存模型(JMM)底層原理

1、內存模型的概述

Java內存模型(即Java Memory Model,簡稱JMM)本身是一種抽象的概念,並不真實存在,它描述的是一組規則或規範,通過這組規範定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。

由於JVM運行程序的實體是線程,而每個線程創建時JVM都會爲其創建一個工作內存(有些地方稱爲棧空間),用於存儲線程私有的數據,而Java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內存中進行。

首先要將變量從主內存拷貝的自己的工作內存空間,然後對變量進行操作,操作完成後再將變量寫回主內存,不能直接操作主內存中的變量,工作內存中存儲着主內存中的變量副本拷貝,前面說過,工作內存是每個線程的私有數據區域,因此不同的線程間無法訪問對方的工作內存,線程間的通信(傳值)必須通過主內存來完成。

2、內存模型的示意圖

主內存:

主要存儲的是Java實例對象,所有線程創建的實例對象都存放在主內存中,不管該實例對象是成員變量還是方法中的本地變量(也稱局部變量),當然也包括了共享的類信息、常量、靜態變量。由於是共享數據區域,多條線程對同一個變量進行訪問可能會發現線程安全問題。

工作內存:

主要存儲當前方法的所有本地變量信息(工作內存中存儲着主內存中的變量副本拷貝),每個線程只能訪問自己的工作內存,即線程中的本地變量對其它線程是不可見的,就算是兩個線程執行的是同一段代碼,它們也會各自在自己的工作內存中創建屬於當前線程的本地變量,當然也包括了字節碼行號指示器、相關Native方法的信息。注意由於工作內存是每個線程的私有數據,線程間無法相互訪問工作內存,因此存儲在工作內存的數據不存在線程安全問題。

3、JMM數據原子操作

4、內存模型工作流程圖

上圖jmm工作流程:

步驟一

線程1:先把initFlag變量從主內存中read讀取出來,再load載入自己的工作內存,use使用線程1執行代碼!initFlag。

步驟二

線程2:先把initFlag變量從主內存中read讀取出來,再load載入自己的工作內存,use使用線程2執行代碼initFlag=true,這時候值發生了改變,線程會再assign重新賦值到工作內存,然後store存儲並寫入主內存,write寫入賦值到主內存中的變量。
(線程2對緩存行lock加鎖,write寫入主內存後會解鎖unlock,防止initFlag還未write寫入主內存就被線程1讀取爲false)。

步驟三

線程1:因爲initFlag被volatile修飾,使用MESI緩存一致性協議,線程1利用cpu總線嗅探機制監聽到了initFlag值的修改,線程1中工作內存initFlag=false失效,這時候線程就拿不到工作內存的數據了,從而工作內存繼續去讀取主內存數據,變爲true退出循環繼續執行,體現了多線程同步運行共享變量副本的可見性。如果initFlag沒有被volatile修飾,線程1將感知不到initFlag的變化,一直循環下去停止不了。

實例代碼:

public class VolatileVisibilityTest {
    //volatile變量,用來確保將變量的更新操作通知到其他線程。
    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("waiting data...");
                while (!initFlag) {

                }
                System.out.println("==============success");
            }
        }).start();

        Thread.sleep(2000);

        new Thread(new Runnable() {
            @Override
            public void run() {
                prepareData();
            }
        }).start();
    }
        public static void prepareData(){
            System.out.println("preparing data...");
            initFlag = true;
            System.out.println("prepare data end...");
        }
}

以上加了“volatile”關鍵字,實現了共享變量的可見性問題

volatile是怎麼實現的呢?原理是怎樣的?

volatile底層原理不是Java來編寫的,而是用彙編語言的來編寫

 

很早以前是用總線的加鎖的機制來做到可見性和一致性的

lock和unlock會對主內存加鎖的,總線加鎖一般不使用,效率太低,跟單線程差不多。一般用MESI緩存一致性協議。

https://www.infoq.cn/article/java-memory-model-1/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章