(7)

Java內存模型JMM

1、什麼是JMM(面試高頻) JVM

​ JMM即爲JAVA 內存模型(java memory model)。因爲在不同的硬件生產商和不同的操作系統下,內存的訪問邏輯有一定的差異,結果就是當你的代碼在某個系統環境下運行良好,並且線程安全,但是換了個系統就出現各種問題。Java內存模型,就是爲了屏蔽系統和硬件的差異,讓一套代碼在不同平臺下能到達相同的訪問結果。JMM從java 5開始的JSR-133發佈後,已經成熟和完善起來。

​ JMM規定了內存主要劃分爲主內存工作內存兩種。此處的主內存和工作內存跟JVM內存劃分(堆、棧、方法區)是在不同的層次上進行的,如果非要對應起來,主內存對應的是Java堆中的對象實例部分,工作內存對應的是棧中的部分區域,從更底層的來說,主內存對應的是硬件的物理內存,工作內存對應的是寄存器和高速緩存。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OAWR6BgX-1575451061221)(assets\1575263339732.png)]

​ JVM在設計時候考慮到,如果JAVA線程每次讀取和寫入變量都直接操作主內存,對性能影響比較大,所以每條線程擁有各自的工作內存,工作內存中的變量是主內存中的一份拷貝,線程對變量的讀取和寫入,直接在工作內存中操作,而不能直接去操作主內存中的變量。但是這樣就會出現一個問題,當一個線程修改了自己工作內存中變量,對其他線程是不可見的,會導致線程不安全的問題。因爲JMM制定了一套標準來保證開發者在編寫多線程程序的時候,能夠控制什麼時候內存會被同步給其他線程。

操作的數據要寫會主存!

內存交互操作

​ 內存交互操作有8種,虛擬機實現必須保證每一個操作都是原子的,不可在分的(對於double和long類型的變量來說,load、store、read和write操作在某些平臺上允許例外)

  • lock (鎖定):作用於主內存的變量,把一個變量標識爲線程獨佔狀態

  • unlock (解鎖):作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定

  • read (讀取):作用於主內存變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用

  • load (載入):作用於工作內存的變量,它把read操作從主存中變量放入工作內存中

  • use (使用):作用於工作內存中的變量,它把工作內存中的變量傳輸給執行引擎,每當虛擬機遇到一個需要使用到變量的值,就會使用到這個指令

  • assign (賦值):作用於工作內存中的變量,它把一個從執行引擎中接受到的值放入工作內存的變量副本中

  • store (存儲):作用於主內存中的變量,它把一個從工作內存中一個變量的值傳送到主內存中,以便後續的write使用

  • write  (寫入):作用於主內存中的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中

JMM對這八種指令的使用,制定瞭如下規則:

    • 不允許read和load、store和write操作之一單獨出現。即使用了read必須load,使用了store必須write
    • 不允許線程丟棄他最近的assign操作,即工作變量的數據改變了之後,必須告知主存
    • 不允許一個線程將沒有assign的數據從工作內存同步回主內存
    • 一個新的變量必須在主內存中誕生,不允許工作內存直接使用一個未被初始化的變量。就是懟變量實施use、store操作之前,必須經過assign和load操作
    • 一個變量同一時間只有一個線程能對其進行lock。多次lock後,必須執行相同次數的unlock才能解鎖
    • 如果對一個變量進行lock操作,會清空所有工作內存中此變量的值,在執行引擎使用這個變量前,必須重新load或assign操作初始化變量的值
    • 如果一個變量沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他線程鎖住的變量
    • 對一個變量進行unlock操作之前,必須把此變量同步回主內存

JMM對這八種操作規則和對volatile的一些特殊規則就能確定哪裏操作是線程安全,哪些操作是線程不安全的了。但是這些規則實在複雜,很難在實踐中直接分析。所以一般我們也不會通過上述規則進行分析。更多的時候,使用java的happen-before規則來進行分析。

2、JMM的內存模型

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NZqNUCBT-1575451061224)(assets\1575263974415.png)]

線程A感知不到線程B操作了值的變化!如何能夠保證線程間可以同步感知這個問題呢?只需要使用Volatile關鍵字即可!volatile 保證線程間變量的可見性,簡單地說就是當線程A對變量X進行了修改後,在線程A後面執行的其他線程能看到變量X的變動,更詳細地說是要符合以下兩個規則 :

  • 線程對變量進行修改之後,要立刻回寫到主內存。
  • 線程對變量讀取的時候,要從主內存中讀,而不是緩存。

各線程的工作內存間彼此獨立,互不可見,在線程啓動的時候,虛擬機爲每個內存分配一塊工作內存,不僅包含了線程內部定義的局部變量,也包含了線程所需要使用的共享變量(非線程內構造的對象)的副本,即,爲了提高執行效率。
volatile是不錯的機制,但是也不能保證原子性。

3、代碼驗證

package JMM;

//Volatile 用來保證數據的同步,也就是可見性
public class JMMVolatileDemo01 {

    // 不加 volatile 就會死循環,這裏給大家將主要是爲了面試,可以避免指令重排
    private volatile static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (num==0){ //此處不要編寫代碼,讓計算機忙的不可開交

            }
        }).start();

        Thread.sleep(1000);
        num = 1;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章