【併發編程】一文帶你讀懂深刻理解Java內存模型(面試必備)

併發編程這一塊內容,是高級資深工程師必備知識點,25K起若是不懂併發編程,那基本到頂。可是併發編程內容龐雜,如何系統學習?本專題將會系統講解併發編程的全部知識點,包括但不限於:java

線程通訊機制,深刻JMM內存模型原理,深刻synchronized原理,深刻volatile原理,DCL,詳解AQS,CAS,可重入鎖,讀寫鎖原理,詳解併發工具類,深刻理解threadLocal,Fork、Join,原子類詳解,Java併發集合詳解(ConcurrentHashMap,ConcurrentLinedQueue,ConcurrentListMap等),阻塞隊列深刻探究,深刻線程池原理及其設計思想。 本文爲深刻理解java內存模型。程序員

零、全文思惟導圖

主線如上圖紅色箭頭,你們能夠先看看總體講的是什麼。java內存模型前面是鋪墊,後面是相關內容。面試

1、引出java內存模型(不作重點講解)

2、那什麼纔會用到java內存模型?

共享變量(實例域,靜態域,數組元素)纔會用到。 局部變量,方法定義參數等不會在線程間共享,因此他們不會有內存可見性問題,也不受內存模型影響編程

3、java內存模型抽象示意圖

Java內存模型簡稱JMM(Java Memory Model),是Java虛擬機所定義的一種抽象規範,用來屏蔽不一樣硬件和操做系統的內存訪問差別,讓java程序在各類平臺下都能達到一致的內存訪問效果。數組

3.1 主內存(Main Memory)

主內存能夠簡單理解爲計算機當中的內存,但又不徹底等同。主內存被全部的線程所共享,對於一個共享變量(好比靜態變量,或是堆內存中的實例)來講,主內存當中存儲了它的「本尊」。緩存

3.2 本地內存(Working Memory)

本地內存能夠簡單理解爲計算機當中的CPU高速緩存,但又不徹底等同。每個線程擁有本身的工做內存,對於一個共享變量來講,工做內存當中存儲了它的「副本」。爲啥有本地內存這個概念?由於直接操做主內存太慢了安全

經過一系列內存讀寫的操做指令(JVM內存模型共定義了8種內存操做指令,之後會細講),線程A把靜態變量 s=0 從主內存讀到工做內存,再把 s=3 的更新結果同步到主內存當中。從單線程的角度來看,這個過程沒有任何問題。多線程

4、指令重排序

理解重排序前這個概念前,咱們先轉換場景,從java內存模型走出來,來到硬件CPU這個維度。併發

4.1基本概念:

在執行程序時爲了提升性能,編譯器和處理器經常會對指令作重排序(簡單理解就是本來咱們寫的代碼指令執行順序應該是A→B→C,可是如今的CPU都是多核CPU,爲了秀下優越,爲了提升並行度,爲了提升性能等,可能會出現指令順序變爲B→A→C等其餘狀況)。app

固然CPU們也不是隨便就去重排序,須要知足如下兩個條件(遵循的規則):

  1. 在單線程環境下不能改變程序運行的結果;
  2. 存在數據依賴關係的不容許重排序

4.2重排序分三類:

  1. 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序。
  2. 指令級並行的重排序。現代處理器採用了指令級並行技術來將多條指令重疊執行。若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序。
  3. 內存系統的重排序。因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行。

從 Java 源代碼到最終實際執行的指令序列,會分別經歷下面三種重排序:

那麼重排序會遵循什麼樣的規則?

5、as-if-serial

5.1as-if-serial語義的意思是:

無論怎麼重排序,(單線程)程序的執行結果不能被改變。編譯器,runtime和處理器都必須遵照as-if-serial語義。OK,這就至關於給CPU們定下規則。不要隨便重排序。要知足我這個as-if-serial的前置條件,才能重排序。

5.2

as-if-serial語義把單線程程序保護了起來,遵照as-if-serial語義的編譯器,runtime和處理器共同爲編寫單線程程序的程序員建立了一個幻覺:單線程程序是按程序的順序來執行的。as-if-serial語義使程序員沒必要擔憂單線程中重排序的問題干擾他們,也無需擔憂內存可見性問題。

注意:as-if-serial只保證單線程環境,多線程環境下無效。那多線程,併發編程下怎麼辦?

6、多線程下致使的問題及解決辦法

上面的這些重排序均可能致使多線程程序出現內存可見性問題,JMM那麼如何解決?

  • 對於編譯器重排序,JMM 的編譯器重排序規則會禁止特定類型的編譯器重排序(不是全部的編譯器重排序都要禁止)。
  • 對於處理器重排序,JMM 的處理器重排序規則會要求 Java 編譯器在生成指令序列時,插入特定類型的內存屏障指令,經過內存屏障指令來禁止特定類型的處理器重排序(不是全部的處理器重排序都要禁止)。

JMM屬於語言級的內存模型,它確保在不一樣的編譯器和不一樣的處理器平臺之上,經過禁止特定類型的編譯器重排序和處理器重排序,爲程序員提供一致的內存可見性保證

7、什麼是內存屏障?

7.1 內存屏障(Memory Barrier)是一種CPU指令‘。

內存屏障也稱爲內存柵欄或柵欄指令,是一種屏障指令,它使CPU或編譯器對屏障指令以前和以後發出的內存操做執行一個排序約束。

7.2 實際運用場景:

volatile即是基於內存屏障實現的。 **觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令。**這個指令就至關於一個內存屏障。具體表現爲:

  • 當寫一個volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量值當即刷新到主內存中。
  • 當讀一個volatile 變量時,JMM 會把該線程對應的本地內存設置爲無效,直接從主內存中讀取共享變量

從而保證了,若是某個線程對volatile修飾的共享變量進行更新,那麼其餘線程能夠立馬看到這個更新,這就是所謂的線程可見性。

(注,關於volatile會在後面單章講解,這裏不過於贅婿)

8、happens-before原則

從jdk5開始,java使用新的JSR-133內存模型,基於happens-before的概念來闡述操做之間的內存可見性。 換句話說,在JMM中,若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必須存在happens-before關係。 happens-before原則是JMM中很是重要的原則,它是判斷數據是否存在競爭、線程是否安全的主要依據,保證了多線程環境下的可見性。 這個的兩個操做既能夠在同一個線程,也能夠在不一樣的兩個線程中。

摘錄一些happens-before規則以下: 一、程序順序規則:一個線程中的每一個操做,happens-before於該線程中任意的後續操做。 二、監視器鎖規則:對一個鎖的解鎖操做,happens-before於隨後對這個鎖的加鎖操做。 三、volatile域規則:對一個volatile域的寫操做,happens-before於任意線程後續對這個volatile域的讀。 四、傳遞性規則:若是 A happens-before B,且 B happens-before C,那麼A happens-before C。 注意:兩個操做之間具備happens-before關係,並不意味前一個操做必需要在後一個操做以前執行!僅僅要求前一個操做的執行結果,對於後一個操做是可見的,且前一個操做按順序排在後一個操做以前。

那麼說了那麼多規則,來看看happens-before與JMM的關係

9、as-if-serial和happens-before小結

  • as-if-serial語義保證單線程內程序的執行結果不被改變
  • happens-before關係保證正確同步的多線程程序的執行結果不被改變。
  • 其實都是爲了在不改變程序執行結果的前提下,儘量地提升程序執行的並行度。

10、扯了那麼久,這幾者如何理解?結論:

  • 重排序是多核CPU等爲了性能進行的優化操做,但會致使可見性等問題。爲了解決這些問題,因此JMM須要制定一些規則,不讓其隨意重排序。
  • as-if-serial只保證單線程環境的不可隨意重排序,那麼多線程下呢?
  • 因此有了happens-before原則,其是JMM(JSR-133內存模型)的規範之一。
  • 內存屏障是CPU指令。
  • 因此說,happens-before是JMM制定的最終目的,內存屏障則是實現happens-before的具體手段。

END

彩蛋小福利

免費獲取Java學習筆記,面試,文檔以及視頻

部分資料以下:

相關文章
相關標籤/搜索