Java線程內存模型,volatile實現原理相關學習筆記

  • minor gc:輕GC,對應用影響很小
  • full gc:針對老年區的清理,jvm優化主要針對full gc

JVM調優:主要是減少full gc的次數,與一次full gc需要的時間

通過gc日誌。

多核併發緩存架構

JAVA線程內存模型

Java線程內存模型與cpu的緩存類似,也是先將主內存中的變量拷貝一份副本到自己的工作內存區域中,線程主要是跟自己的工作內存打交道的。

因爲拷貝一份副本變量到自己的工作內存(這部分內存是線程私有的),所以其他線程是看不到這份變量的變化的,volatile的作用就是:將某個變量的變化,讓所有線程都看到。他的實現方式是:強制要求線程直接與主內存進行讀寫操作。

###JMM數組原子操作

  • lock(鎖定):作用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。
  • unlock(解鎖):作用於主內存變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。
  • read(讀取):作用於主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用
  • load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
  • use(使用):作用於工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。
  • assign(賦值):作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
  • store(存儲):作用於工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨後的write的操作。
  • write(寫入):作用於主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中。

一次多線程操作,JMM層面的操作過程

代碼

private static boolean flag = false;
    public static void main(String[] args) throws InterruptedException {

        new Thread(()->{
            System.out.println("start a ...");
            while (!flag) { }
            System.out.println("flag == true end");
        }).start();

        Thread.sleep(2000);

        new Thread(() -> {
            System.out.println("start b ...");
            flag = true;
            System.out.println("b end...");
        }).start();
    }

執行過程如下:

  • 線程a簡稱a,線程b簡稱b
  • 左邊的線程a:JVM先將flag從主內存中read出來,然後通過load,將值複製一份到工作內存,然後use工作內存中的副本變量給cpu,cpu計算完畢後發現while循環爲true,則繼續下去。
  • 右邊的線程b:JVM陷阱flag從主內存中read出來,然後通過load,將值複製一份到工作內存,然後use工作內存中的副本變量給cpu,這時候需要做個賦值操作,使用assign將值賦給工作內存中的副本變量,然後使用store將變量傳到主內存中,最後使用write將值賦給主內存中的flag變量。
  • 所以整個過程,a中一直使用的是自己工作內存中的變量flag,外界(b中工作內存flag與主內存中flag)的變量發生變化,a是不知道的,只能傻傻地循環下去。
工作內存

是cache和寄存器的一個抽象,並不是內存的某個部分

實現線程間變量可見性的原理,有兩種方式

  • 總線加鎖(性能低,早期),這是最開始解決線程間變量的可見性問題的解決方案。
    • cpu從主內存讀取數據到高速緩存,會在總線對這個數據加鎖,這樣其他cpu沒法讀或寫這個數據,直到這個cpu使用完數據釋放鎖之後其他cpu才能讀取到這個數據
  • mesi緩存一致性協議
    • 多個cpu從主內存讀取同一個數據到各自的高速緩存,當其中某個cpu修改了緩存裏的數據,該數據會馬上同步回主內存,其他cpu通過總線嗅探機制可以感知到數據的變化從而將自己緩存裏的數據失效,cpu發現緩存失效了就會從主內存重新load一份數據到自己的高速緩存。

內存與cpu交互中間是有一層總線的,他是一個物理硬件。

volatile實現可見性的原理

volatile是基於mesi緩存一致性協議實現的多線程變量的可見性。

底層實現主要依靠彙編lock前綴指令,他會鎖定這塊內存區域的緩存(緩存行鎖定),並寫回到主內存。

lock的四個邏輯:

  • 鎖定主內存的那塊內存區域的數據(緩存行鎖定),也就是在store前加鎖,store->write完畢後,解鎖

  • 會將當前處理器緩存行的數據立即寫回到主內存

  • 這個寫回內存的操作會引起在其他CPU裏緩存了該內存地址的數據無效(結合mesi協議)

  • 原生語意有內存屏障的功能,也就是禁止指令重排序(這是實現了有序性)

小結

  • 這裏顯示出了mesi緩存一致性協議較於總線加鎖的優勢,降低鎖粒度
    • 總線加鎖是在數據計算之前進行加鎖,然後開始計算,計算的時候其他cpu都需要一直等待。
    • lock +mesi 則會在cpu計算完畢後,準備將變量store->write回主內存之前加鎖,這樣就大大減少了鎖住的時間(因爲少了cpu計算)

單核情況下需要使用volatile或者synchronized嗎

首先,JMM屏蔽了硬件細節,是無關單核還是多核的。

在單核cpu上,線程們通過時間片輪流執行,同一進程中的不同線程交替使用cpu時,訪問的是同一塊共享緩存,這樣就不需要通過協調主內存(比如多核中,volatile的lock前綴,可以短暫的鎖住主內存區域)來進行線程間的通信,所以volatile的可見性就用不着了。

但volatile的lock前綴的讀寫屏障可以禁止volatile變量之間的重排序的作用在併發時是需要的。

synchronized同樣可見性作用不大,但原子性是很必要的。

java多線程在單核CPU上,還是需要volatile synchronized嗎?

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