溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java內存模型的知識點有哪些

發(fā)布時間:2022-01-07 11:43:25 來源:億速云 閱讀:102 作者:iii 欄目:編程語言

這篇文章主要介紹“Java內存模型的知識點有哪些”,在日常操作中,相信很多人在Java內存模型的知識點有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java內存模型的知識點有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

關于 Java  內存模型,我們還是先從硬件內存模型入手。

硬件內存模型

先來看看硬件內存簡單架構,如下圖所示:

Java內存模型的知識點有哪些

硬件內存結構

這是一幅簡單的硬件內存結構圖,真實的結構圖要比這復雜很多,特別是在緩存層,現在的計算機中 CPU 緩存一般有三層,你也可以打開你的電腦看看,打開  任務資源管理器 ---> 性能 ---> cpu ,如下圖所示:

Java內存模型的知識點有哪些 

CPU 緩存

從圖中可以看出我這臺機器的 CPU 有三級緩存,一級緩存 (L1) 、二級緩存(L2)、三級緩存(L3),一級緩存是最接近 CPU  的,三級緩存是最接近內存的,每一級緩存的數據都是下一級緩存的一部分。三級緩存架構如下圖所示:

Java內存模型的知識點有哪些 

現在我們對硬件內存架構有了一定的了解,我們來弄明白一個問題,為什么需要在 CPU 和內存之間添加緩存?

關于這個問題我們就簡單點說,我們知道 CPU 是高速的,而內存相對來說是低速的,這就會造成一個問題,不能充分的利用 CPU 高速的特點,因為 CPU  每次從內存里獲取數據的話都需要等待,這樣就浪費了 CPU 高速的性能,緩存的出現就是用來消除 CPU 與內存之間差距的。緩存的速度要大于內存小于 CPU  ,加入緩存之后,CPU 直接從緩存中讀取數據,因為緩存還是比較快的,所以這樣就充分利用了 CPU  高速的特性。但也不是每次都能從緩存中讀取到數據,這個跟我們項目中使用的 redis 等緩存工具一樣,也存在一個緩存命中率,在 CPU 中,先查找 L1  Cache,如果 L1 Cache 沒有命中,就往 L2 Cache 里繼續(xù)找,依此類推,最后沒找到的話直接從內存中取,然后添加到緩存中。當然當 CPU  需要寫數據到主存時,同樣會先刷新寄存器中的數據到 CPU 緩存,然后再把數據刷新到主內存中。

也許你已經看出了這個框架的弊端,在單核時代只有一個處理器核心,讀/寫操作完全都是由單核完成,沒什么問題;但是多核架構,一個核修改主存后,其他核心并不知道數據已經失效,繼續(xù)傻傻的使用主存或者自己緩存層的數據,那么就會導致數據不一致的情況。關于這個問題  CPU 硬件廠商也提供了解決辦法,叫做緩存一致性協(xié)議(MESI 協(xié)議),緩存一致性協(xié)議這東西我也不了解,我也說不清,所以就不在這里 BB  了,有興趣的可以自行研究。

聊完了硬件內存架構,我們將焦點回到我們的主題 Java 內存模型上,下面就一起來聊一聊 Java 內存模型。

Java 內存模型

Java 內存模型是什么?Java 內存模型可以理解為遵照多核硬件架構的設計,用 Java 實現了一套 JVM  層面的“緩存一致性”,這樣就可以規(guī)避 CPU 硬件廠商的標準不一樣帶來的風險。好了,正式介紹一下 Java 內存模型:Java 內存模型 ( Java  Memory Model,簡稱 JMM ),本身是種抽象的概念,并不是像硬件架構一樣真實存在的,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個變量  (包括實例字段、靜態(tài)字段和構成數組對象的元素) 的訪問方式,更多關于 Java 內存模型知識可以閱讀 JSR 133 :Java 內存模型與線程規(guī)范。

我們知道 JVM 運行程序的實體是線程,在上一篇 JVM 內存結構中我們得知每個線程創(chuàng)建時,JVM 都會為其創(chuàng)建一個工作內存 ( Java 棧  ),用于存儲線程私有數據,而 Java 內存模型中規(guī)定所有變量都存儲在主內存,主內存是共享內存區(qū)域,所有線程都可以訪問,但線程對變量的操作 ( 讀取賦值等 )  必須在工作內存中進行,首先要將變量從主內存拷貝到自己的工作內存空間,然后對變量進行操作,操作完后再將變量寫回主內存,不能直接操作主內存中的變量。

我們知道 Java 棧是每個線程私有的數據區(qū)域,別的線程無法訪問到不同線程的私有數據,所以線程需要通信的話,就必須通過主內存來完成,Java  內存模型就是夾在這兩者之間的一組規(guī)范,我們先來看看這個抽象架構圖:

Java內存模型的知識點有哪些

圖片來源網絡

從結構圖來看,如果線程 A 與線程 B 之間需要通信的話,必須要經歷下面 2 個步驟:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)

  2. 首先,線程 A 把本地內存 A 中的共享變量副本中的值刷新到主內存中去。

  3. 然后,線程 B 到主內存中去讀取線程 A 更新之后的值,這樣線程 A 中的變量值就到了線程 B 中。

我們來看一個具體的例子來加深一下理解,看下面這張圖:

Java內存模型的知識點有哪些

現在線程 A 需要和線程 B 通信,我們已經知道線程之間通信的兩部曲了,假設初始時,這三個內存中的 x 值都為 0。線程 A 在執(zhí)行時,把更新后的 x  值(假設值為 1)臨時存放在自己的本地內存 A 中。當線程 A 和線程 B 需要通信時,線程 A 首先會把自己本地內存中修改后的 x  值刷新到主內存中,此時主內存中的 x 值變?yōu)榱?1。隨后,線程 B 到主內存中去讀取線程 A 更新后的 x 值,此時線程 B 的本地內存的 x 值也變?yōu)榱? 1,這樣就完成了一次通信。

JMM 通過控制主內存與每個線程的本地內存之間的交互,來為 Java 程序員提供內存可見性保證。Java  內存模型除了定義了一套規(guī)范,還提供了一系列原語,封裝了底層實現后,供開發(fā)者直接使用。這套實現也就是我們常用的volatile、synchronized、final  等。

Happens-Before內存模型

Happens-Before 內存模型或許叫做 Happens-Before 原則更為合適,在 《JSR 133  :Java 內存模型與線程規(guī)范》中,Happens-Before 內存模型被定義成 Java 內存模型近似模型,Happens-Before  原則要說明的是關于可見性的一組偏序關系。

為了方便程序員開發(fā),將底層的繁瑣細節(jié)屏蔽掉,Java 內存模型 定義了 Happens-Before 原則。只要我們理解了 Happens-Before  原則,無需了解 JVM 底層的內存操作,就可以解決在并發(fā)編程中遇到的變量可見性問題。JVM 定義的 Happens-Before  原則是一組偏序關系:對于兩個操作 A 和 B,這兩個操作可以在不同的線程中執(zhí)行。如果 A Happens-Before B,那么可以保證,當 A  操作執(zhí)行完后,A 操作的執(zhí)行結果對 B 操作是可見的。

Happens-Before 原則一共包括 8 條,下面我們一起簡單的學習一下這 8 條規(guī)則。

1、程序順序規(guī)則

這條規(guī)則是指在一個線程中,按照程序順序,前面的操作 Happens-Before  于后續(xù)的任意操作。這一條規(guī)則還是非常好理解的,看下面這一段代碼

class Test{ 1   int x ; 2   int y ; 3   public void run(){ 4       y = 20; 5       x = 12;     } }

第四行代碼要 Happens-Before 于第五行代碼,也就是按照代碼的順序來。

2、鎖定規(guī)則

這條規(guī)則是指對一個鎖的解鎖 Happens-Before  于后續(xù)對這個鎖的加鎖。例如下面的代碼,在進入同步塊之前,會自動加鎖,而在代碼塊執(zhí)行完會自動釋放鎖,加鎖以及釋放鎖都是編譯器幫我們實現的

synchronized (this) {     // 此處自動加鎖     // x 是共享變量, 初始值 =10     if (this.x < 12) {        this.x = 12;     } } // 此處自動解鎖

對于鎖定規(guī)則可以這樣理解:假設 x 的初始值是 10,線程 A 執(zhí)行完代碼塊后 x 的值會變成 12(執(zhí)行完自動釋放鎖),線程 B  進入代碼塊時,能夠看到線程 A 對 x 的寫操作,也就是線程 B 能夠看到 x==12。

3、volatile 變量規(guī)則

這條規(guī)則是指對一個 volatile 變量的寫操作及這個寫操作之前的所有操作 Happens-Before  對這個變量的讀操作及這個讀操作之后的所有操作。

4、線程啟動規(guī)則

這條規(guī)則是指主線程 A 啟動子線程 B 后,子線程 B 能夠看到主線程在啟動子線程 B 前的操作。

public class Demo {     private static int count = 0;     public static void main(String[] args) throws InterruptedException {         Thread t1 = new Thread(() -> {             System.out.println(count);         });         count = 12;         t1.start();     } }

子線程 t1 能夠看見主線程對 count 變量的修改,所以在線程中打印出來的是 12 。這也就是線程啟動規(guī)則

5、線程結束規(guī)則

這條是關于線程等待的。它是指主線程 A 等待子線程 B 完成(主線程 A 通過調用子線程 B 的 join() 方法實現),當子線程 B  完成后(主線程 A 中 join() 方法返回),主線程能夠看到子線程的操作。當然所謂的“看到”,指的是對共享變量的操作。

public class Demo {     private static int count = 0;     public static void main(String[] args) throws InterruptedException {         Thread t1 = new Thread(() -> {             // t1 線程修改了變量             count = 12;         });         t1.start();         t1.join();         // mian 線程可以看到 t1 線程改修后的變量         System.out.println(count);     } }

6、中斷規(guī)則

一個線程在另一個線程上調用 interrupt ,Happens-Before 被中斷線程檢測到 interrupt 被調用。

public class Demo {     private static int count = 0;     public static void main(String[] args) throws InterruptedException {         Thread t1 = new Thread(() -> {             // t1 線程可以看到被中斷前的數據             System.out.println(count);         });         t1.start();         count = 25;         // t1 線程被中斷         t1.interrupt();     } }

mian 線程中調用了 t1 線程的 interrupt() 方法,mian 對 count 的修改對 t1 線程是可見的。

7、終結器規(guī)則

一個對象的構造函數執(zhí)行結束 Happens-Before 它的  finalize()方法的開始?!敖Y束”和“開始”表明在時間上,一個對象的構造函數必須在它的  finalize()方法調用時執(zhí)行完。根據這條原則,可以確保在對象的 finalize 方法執(zhí)行時,該對象的所有 field 字段值都是可見的。

8、傳遞性規(guī)則

這條規(guī)則是指如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens- Before  C。

到此,關于“Java內存模型的知識點有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI