1. 程式人生 > >硬體記憶體模型到 Java 記憶體模型,這些硬核知識你知多少?

硬體記憶體模型到 Java 記憶體模型,這些硬核知識你知多少?

Java 記憶體模型跟上一篇 JVM 記憶體結構很像,我經常會把他們搞混,但其實它們不是一回事,而且相差還很大的,希望你沒它們搞混,特別是在面試的時候,搞混了的話就會答非所問,影響你的面試成績,當然也許你碰到了半吊子面試官,那就要恭喜你了。Java 記憶體模型比 JVM 記憶體結構複雜很多,Java 記憶體模型有一個規範叫:《JSR 133 :Java記憶體模型與執行緒規範》,裡面的內容很豐富,如果你沒看過的話,我建議你看一下。今天我們就簡單的來聊一聊 Java 記憶體模型,關於 Java 記憶體模型,我們還是先從硬體記憶體模型入手。

硬體記憶體模型

先來看看硬體記憶體簡單架構,如下圖所示:

這是一幅簡單的硬體記憶體結構圖,真實的結構圖要比這複雜很多,特別是在快取層,現在的計算機中 CPU 快取一般有三層,你也可以開啟你的電腦看看,開啟 任務資源管理器 ---> 效能 ---> cpu ,如下圖所示:

從圖中可以看出我這臺機器的 CPU 有三級快取,一級快取 (L1) 、二級快取(L2)、三級快取(L3),一級快取是最接近 CPU 的,三級快取是最接近記憶體的,每一級快取的資料都是下一級快取的一部分。三級快取架構如下圖所示:

現在我們對硬體記憶體架構有了一定的瞭解,我們來弄明白一個問題,為什麼需要在 CPU 和記憶體之間新增快取?

關於這個問題我們就簡單點說,我們知道 CPU 是高速的,而記憶體相對來說是低速的,這就會造成一個問題,不能充分的利用 CPU 高速的特點,因為 CPU 每次從記憶體裡獲取資料的話都需要等待,這樣就浪費了 CPU 高速的效能,快取的出現就是用來消除 CPU 與記憶體之間差距的。快取的速度要大於記憶體小於 CPU ,加入快取之後,CPU 直接從快取中讀取資料,因為快取還是比較快的,所以這樣就充分利用了 CPU 高速的特性。但也不是每次都能從快取中讀取到資料,這個跟我們專案中使用的 redis 等快取工具一樣,也存在一個快取命中率,在 CPU 中,先查詢 L1 Cache,如果 L1 Cache 沒有命中,就往 L2 Cache 裡繼續找,依此類推,最後沒找到的話直接從記憶體中取,然後新增到快取中。當然當 CPU 需要寫資料到主存時,同樣會先重新整理暫存器中的資料到 CPU 快取,然後再把資料重新整理到主記憶體中。

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

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

Java 記憶體模型

Java 記憶體模型是什麼?Java 記憶體模型可以理解為遵照多核硬體架構的設計,用 Java 實現了一套 JVM 層面的“快取一致性”,這樣就可以規避 CPU 硬體廠商的標準不一樣帶來的風險。好了,正式介紹一下 Java 記憶體模型:Java 記憶體模型 ( Java Memory Model,簡稱 JMM ),本身是種抽象的概念,並不是像硬體架構一樣真實存在的,它描述的是一組規則或規範,通過這組規範定義了程式中各個變數 (包括例項欄位、靜態欄位和構成陣列物件的元素) 的訪問方式,更多關於 Java 記憶體模型知識可以閱讀 JSR 133 :Java記憶體模型與執行緒規範。

我們知道 JVM 執行程式的實體是執行緒,在上一篇 JVM 記憶體結構中我們得知每個執行緒建立時,JVM 都會為其建立一個工作記憶體 ( Java 棧 ),用於儲存執行緒私有資料,而 Java 記憶體模型中規定所有變數都儲存在主記憶體,主記憶體是共享記憶體區域,所有執行緒都可以訪問,但執行緒對變數的操作 ( 讀取賦值等 ) 必須在工作記憶體中進行,首先要將變數從主記憶體拷貝到自己的工作記憶體空間,然後對變數進行操作,操作完後再將變數寫回主記憶體,不能直接操作主記憶體中的變數。

我們知道 Java棧是每個執行緒私有的資料區域,別的執行緒無法訪問到不同執行緒的私有資料,所以執行緒需要通訊的話,就必須通過主記憶體來完成,Java 記憶體模型就是夾在這兩者之間的一組規範,我們先來看看這個抽象架構圖:

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

  1. 首先,執行緒 A 把本地記憶體 A 中的共享變數副本中的值重新整理到主記憶體中去。
  2. 然後,執行緒 B 到主記憶體中去讀取執行緒 A 更新之後的值,這樣執行緒 A 中的變數值就到了執行緒 B 中。

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

現線上程 A 需要和執行緒 B 通訊,我們已經知道執行緒之間通訊的兩部曲了,假設初始時,這三個記憶體中的 x 值都為 0。執行緒 A 在執行時,把更新後的 x 值(假設值為 1)臨時存放在自己的本地記憶體 A 中。當執行緒 A 和執行緒 B 需要通訊時,執行緒 A 首先會把自己本地記憶體中修改後的 x 值重新整理到主記憶體中,此時主記憶體中的 x 值變為了 1。隨後,執行緒 B 到主記憶體中去讀取執行緒 A 更新後的 x 值,此時執行緒 B 的本地記憶體的 x 值也變為了 1,這樣就完成了一次通訊。

JMM 通過控制主記憶體與每個執行緒的本地記憶體之間的互動,來為 Java 程式設計師提供記憶體可見性保證。Java 記憶體模型除了定義了一套規範,還提供了一系列原語,封裝了底層實現後,供開發者直接使用。這套實現也就是我們常用的volatilesynchronizedfinal 等。

Happens-Before 記憶體模型

Happens-Before 記憶體模型或許叫做 Happens-Before 原則更為合適,在 《JSR 133 :Java記憶體模型與執行緒規範》中,Happens-Before 記憶體模型被定義成 Java 記憶體模型近似模型,Happens-Before 原則要說明的是關於可見性的一組偏序關係。

為了方便程式設計師開發,將底層的煩瑣細節遮蔽掉,Java 記憶體模型 定義了 Happens-Before 原則。只要我們理解了Happens-Before 原則,無需瞭解 JVM 底層的記憶體操作,就可以解決在併發程式設計中遇到的變數可見性問題。JVM 定義的 Happens-Before 原則是一組偏序關係:對於兩個操作A和B,這兩個操作可以在不同的執行緒中執行。如果A Happens-Before B,那麼可以保證,當A操作執行完後,A操作的執行結果對B操作是可見的。

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

1、程式順序規則

這條規則是指在一個執行緒中,按照程式順序,前面的操作 Happens-Before 於後續的任意操作。這一條規則還是非常好理解的,看下面這一段程式碼

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

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

2、鎖定規則

這條規則是指對一個鎖的解鎖 Happens-Before 於後續對這個鎖的加鎖。例如下面的程式碼,在進入同步塊之前,會自動加鎖,而在程式碼塊執行完會自動釋放鎖,加鎖以及釋放鎖都是編譯器幫我們實現的

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

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

3、volatile變數規則

這條規則是指對一個 volatile 變數的寫操作及這個寫操作之前的所有操作 Happens-Before 對這個變數的讀操作及這個讀操作之後的所有操作。

4、執行緒啟動規則

這條規則是指主執行緒 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 。這也就是執行緒啟動規則

5、執行緒結束規則

這條是關於執行緒等待的。它是指主執行緒 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、中斷規則

一個執行緒在另一個執行緒上呼叫 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、終結器規則

一個物件的建構函式執行結束Happens-Before它的finalize()方法的開始。“結束”和“開始”表明在時間上,一個物件的建構函式必須在它的finalize()方法呼叫時執行完。根據這條原則,可以確保在物件的finalize方法執行時,該物件的所有field欄位值都是可見的。

8、傳遞性規則

這條規則是指如果 A Happens-Before B,且 B Happens-Before C,那麼 A Happens- Before C。

最後

目前網際網路上很多大佬都有 Java 記憶體模型系列教程,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支援。若文中有所錯誤之處,還望提出,謝謝,歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,和平頭哥一起學習,一起進步。

相關推薦

硬體記憶體模型Java 記憶體模型這些知識多少?

Java 記憶體模型跟上一篇 JVM 記憶體結構很像,我經常會把他們搞混,但其實它們不是一回事,而且相差還很大的,希望你沒它們搞混,特別是在面試的時候,搞混了的話就會答非所問,影響你的面試成績,當然也許你碰到了半吊子面試官,那就要恭喜你了。Java 記憶體模型比 JVM 記憶體結構複雜很多,Java 記憶體模

十二、JAVA多執行緒:機器硬體CPU、Java記憶體模型

  大家都知道,計算機在執行程式時,每條指令都是在CPU中執行的,而執行指令過程中,勢必涉及到資料的讀取和寫入。由於程式執行過程中的臨時資料是存放在主存(實體記憶體)當中的,這時就存在一個問題,由於CPU執行速度很快,而從記憶體讀取資料和向記憶體寫入資料的過程跟CPU執行指令的速度比起來要慢的多,因

三、Java記憶體模型---Java記憶體模型的基礎

3.1 Java記憶體模型的基礎 3.1.1 併發程式設計模型的兩個關鍵問題 併發程式設計中,有兩大關鍵問題:執行緒之間如何通訊和執行緒之間如何同步。通訊是指執行緒之間以何種機制來交換資訊。在指令式程式設計中,執行緒之間的通訊機制有兩種:共享記憶體和訊息傳遞。 在共享記憶體的併發模

Java記憶體模型-Java記憶體模型中的順序一致性

         如果程式是正確同步的,程式的執行將具有順序一致性(Sequentially Consistent)——即程式的執行結果與該程式在順序一致性記憶體模型中的執行結果相同。         &n

Java記憶體模型-Java記憶體模型的基礎

                                         Java

Java這麼多年這些祕密知道嗎?

對於這些祕密中的每一個,重要的是要注意它們中的一些,例如數字下劃線和快取自動裝箱在應用程式中可能是有用的,但是其他的(如單個Java檔案中的多個類)已被降級到backburner一個原因。因此,僅僅因為語言中存在的功能並不 意味著它應該被使用(即使它不被棄用)。相反,判斷應該用於何時應用這些隱藏功能。在研究好

Java程序員這些開源工具必須要學會

選擇 報告 存儲 python https scrum 裝系統 dock 面試題 前言 本文主要介紹Java程序員應該在2018年學習的一些基本和高級工具。如果你是一位經驗豐富的Java開發人員,擁有5到10年的經驗,你可能對這些工具很熟悉,但如果不是,現在就是是開始學習這

JRockit檢測Tomcat記憶體溢位JAVA記憶體洩漏問題

公司的一個JAVA應用系統上線以來,基本每1天OutOfMemoryError: PermGen space一次。JAVA堆上限1個G,出現了好多次,想到JRockit可以分析記憶體洩漏的問題: 1. JRockit簡介 Jrockit是Bea開發的符合JAVA虛擬機器規範的虛擬機器+虛擬機器監控軟體。 虛

Android記憶體基礎——Java記憶體管理機制

參考連結 參考資料1 背景介紹 Java優勢之一就是其具有垃圾回收機制。在大部分情況下,JVM的GC(垃圾回收器)能夠幫助我們回那些不可到達的物件(就是未被引用的物件)。 當然,在一些情況下

200道Java技術答疑阿里技術專家幫Java技術進階

雲棲社群邀請到6位Java技術專家幫開發者答疑解惑,其中精華的200道問答已經整理出來,供大家學習! 如有Java相關問題,請向專家提問https://yq.aliyun.com/promotion/755 spring相關問題 [@古散]用spring boot 寫後臺可以完全用kotlin代替Ja

11月程式語言排行榜新鮮出爐這些經典圖書等來撩!

​再過兩個月,TIOBE 就將宣佈 2018 年的年度程式語言。年度語言有望出現在前 5 名:Java、C、C+、Python 和 Visual Basic.NET 中。每年,我們都希望一些有創意特性的程式語言獲獎,但是 2018 年度語言桂冠很可能由一個成熟的語言摘得。 當然,今年也有出現了一

"2018年Java程式設計師風光背後的危機"——知道程式設計師的現狀嗎?

近日網上有一篇關於Java程式設計師職場生存現狀的文章“2018年 Java 程式設計師,風光背後的危機”,在Java程式設計師圈子裡引起了廣泛關注和熱議。 2018年,Java 程式設計師面臨更加激烈的競爭。 不得不承認,經歷過行業的飛速發展期,網際網路的整體發展趨於

作為程式設計師無論在哪個階段這些書都值得

我們都想要自己的程式設計技能能上升到更高級別的水平,但往往不知道從何下手,本文,我將推薦6本書,無論是是什麼程式設計師,這些書都可以讓你的能力得到提升 1、《程式碼整潔之道》 這本書是我整個職業生涯中讀過最好的書之一,讀完本書,你會更加清楚編寫乾淨程式碼的重要性,從變

2019年一半已過這些大前端技術都GET了嗎?- 上篇

一晃眼2019年已過大半,年初信誓旦旦要學習新技能的小夥伴們立的flag都完成的怎樣了?2019年對於大前端技術領域而言變化不算太大,目前三大技術框架日趨成熟,短期內不大可能出現顛覆性的前端框架(內心OS:出了也學不動了)。 本文結合個人和團隊經歷對2019上半年做個技術總結,將各類技術框架/語言/工具分作

2019年一半已過這些大前端技術都GET了嗎?- 下篇

在上一篇文章中已經介紹了大前端關於狀態管理、UI元件、小程式、跨平臺和框架層的內容。在本文中,我會繼續介紹程式語言、工程化、監控、測試和服務端,同時也會對下半年大前端可以關注的部分進行展望。 結合個人和團隊經歷對2019上半年做個技術總結,將各類技術框架/語言/工具分作兩個維度: 技術採用生命週期 技術

Java 網路爬蟲需要哪些基礎知識

說起網路爬蟲,大家想起的估計都是 Python ,誠然爬蟲已經是 Python 的代名詞之一,相比 Java 來說就要遜色不少。有不少人都不知道 Java 可以做網路爬蟲,其實 Java 也能做網路爬蟲而且還能做的非常好,在開源社群中有不少優秀的 Java 網路爬蟲框架,例如 webmagic 。我的第一份正

程式設計師需要了解的知識記憶體

我們都知道,計算機是處理資料的裝置,而資料的主要儲存位置就是磁碟和記憶體,並且對於程式設計師來講,CPU 和記憶體是我們必須瞭解的兩個物理結構,它是你通向高階程式設計師很重要的橋樑,那麼本篇文章我們就來介紹一下基本的記憶體知識。 什麼是記憶體 記憶體(Memory)是計算機中最重要的部件之一,它是程式與CPU

看過無數Java GC文章這5個問題也未必知道!

看過無數Java GC文章,這6個問題你也未必知道! 讀者朋友們可能已經看過太多關於Java垃圾回收相關的文章,如果沒有,牆裂安利大家看下面這篇: [看完這篇垃圾回收,和麵試官扯皮沒問題了](https://mp.weixin.qq.com/s/8vXENzg580R7F2iNjSdHFw) 本文不再重複

程式設計師需要了解的知識之控制硬體

應用和硬體的關係 我們作為程式設計師一般很少直接操控硬體,我們一般通過 C、Java 等高階語言編寫的程式起到間接控制硬體的作用。所以大家很少直接接觸到硬體的指令,硬體的控制是由 Windows 作業系統 全權負責的。 你一定猜到我要說什麼了,沒錯,我會說但是,任何事情沒有絕對性,環境的不同會造成結果的偏差。

只看到了別人28歲從位元組跳動退休背後的期權知識知道嗎?

前一陣位元組跳動程式設計師,年僅 28 歲的郭宇財務自由,宣佈退休的訊息在網上火了一把,這個事情大家應該都知道了,細節就不多說了。 我剛看了這個新聞之後,起初內心出現的幾個關鍵詞是:臥槽、牛、羨慕……在意識到瞎感慨、羨慕沒啥用之後,我心裡不禁有個疑問:他的錢是怎麼來的? 我找在位元組上班的朋友打聽了一下,