- 臨近秋招,備戰暑期實習,祝大家每天進步億點點!Day19
- 本篇總結的是 JMM記憶體模型,volatile 關鍵字保證有序性和可見性的原理,happens-before原則,

參考文章:Java面試官告訴你JMM是什么和面什么、阿里實習面經、「阿里面試系列」分析Synchronized原理,讓面試官仰望、happens-before理解和應用、面試官:說說什么是 Java 記憶體模型(JMM)?、面試官:volatile是如何保證可見性和有序性的?、happen-before原則、通俗易懂講解happens-before原則
1、什么是Java記憶體模型?
首先,要知道,Java 記憶體模型指的是 JMM,而不是運行時資料區哦~
-
Java 語言為了保證并發編程中可以滿足原子性、可見性及有序性,于是推出了一個概念就是 JMM 記憶體模型,
-
JMM 記憶體模型,目的是為了在多執行緒條件下,使用共享記憶體進行資料通信時,通過對多執行緒程式讀操作、寫操作行為規范約束,來盡量避免多次記憶體資料讀取不一致、編譯器對代碼指令重排序、處理器對代碼亂序執行等帶來的問題,
- JMM 記憶體模型解決并發問題主要采用兩種方式:
限制處理器優化和使用記憶體屏障, - JMM 記憶體模型將記憶體主要劃分為主記憶體和作業記憶體兩種,規定 所有的變數都存盤在主記憶體中,每條執行緒都擁有自己的作業記憶體,執行緒的作業記憶體中保存了該執行緒所需要用到的變數在主記憶體中的副本拷貝,執行緒對變數的所有操作都必須在作業記憶體中進行,而不能直接讀、寫主記憶體,
- 不同的執行緒之間也無法直接訪問對方作業記憶體中的變數,執行緒間變數的傳遞均需要執行緒自己的作業記憶體和主存之間進行資料互動,
如圖所示:
- JMM 記憶體模型解決并發問題主要采用兩種方式:

JMM 記憶體模型作業記憶體、主記憶體和 JVM 記憶體有什么關系?
JMM 記憶體模型中,作業記憶體和主記憶體其實跟JVM記憶體的劃分是在不同層次上進行的,是自己的一套抽象概念,大概可以理解為,主記憶體對應的是 Java 堆中的物件實體部分,而作業記憶體對應的則是堆疊中的部磁區域,
2、JMM 定義了哪些操作來完成主記憶體和作業記憶體的互動操作?
JMM 定義了8 個操作來完成主記憶體和作業記憶體的互動操作:
- ① 首先是從
lock加鎖開始,作用于主記憶體的變數,把一個變數標識為一條執行緒獨占的狀態; - ②
read讀取,作用于主記憶體變數,將一個變數的值從主記憶體讀取到作業記憶體中; - ③
load加載,作用于作業記憶體的變數,把read讀取到的值加載到作業記憶體的變數副本中; - ④
use使用,作用于作業記憶體的變數,把作業記憶體中變數的值傳遞給執行引擎使用,每當虛擬機遇到一個需要使用變數值的位元組碼指令時將會執行這個操作; - ⑤
assign賦值,作用于作業記憶體的變數,把從執行引擎接收到的值賦值給作業記憶體的變數,每當虛擬機遇到一個需要使用變數值的位元組碼指令時將會執行這個操作; - ⑥
store存盤,作用于作業記憶體的變數,把作業記憶體中變數的值傳送回主記憶體中,以便隨后的write的操作; - ⑦
write寫入,作用于主記憶體的變數,把store得到的值放入主記憶體的變數中; - ⑧ 最后是
unlock解鎖,把主記憶體中處于鎖定狀態的變數釋放出來,流程到這一步就結束了,
如圖所示:

JMM 基本可以說是圍繞著在并發中如何處理這三個特性而建立起來的,也就是原子性、可見性、以及有序性,
3、volatile關鍵字是如何保證可見性的?
-
當一個共享變數被
volatile修飾時,它會保證修改的值會被立即更新到主記憶體中,當有其他執行緒讀取該值時,也不會直接讀取作業記憶體中的值,而是直接去主記憶體中讀取, -
而普通的共享變數不能保證可見性的,因為普通共享變數被修改后,寫入了作業記憶體中,什么時候寫入主記憶體其實是不可知的,當其他執行緒去讀取是,此時無論是作業記憶體還是主記憶體,可能還是原來的值,因此無法保證可見性,
被volatile關鍵字修飾的變數,在每個寫操作之后,都會加入一條store記憶體屏障命令,此命令強制將此變數的最新值從作業記憶體同步至主記憶體;在每個讀操作之前,都會加入一條load記憶體屏障命令,此命強制從主記憶體中將此變數的最新值加載至當前執行緒的作業記憶體中,
4、volatile關鍵字是如何保證有序性的?
volatile 可以禁止指令重排,保證程式會嚴格按照代碼的先后順序執行,
加了volatile 修飾的共享變數,通過記憶體屏障解決多執行緒下的有序性問題,原理如下:
- 在每個 volatile 寫操作的前面插入一個 StoreStore 屏障
- 在每個 volatile 寫操作的后面插入一個StoreLoad屏障
- 在每個 volatile 讀操作的后面插入一個LoadLoad屏障
- 在每個 volatile 讀操作的后面插入一個LoadStore屏障
volatile 在寫操作前后插入了記憶體屏障后生成的指令序列示意圖如下:

volatile 在讀操作后面插入了記憶體屏障后生成的指令序列示意圖如下:

5、happens-before原則
happens-before 的概念:
JMM 可以通過 happens-before 關系向程式員提供跨執行緒的記憶體可見性保證(如果 A 執行緒的寫操作 a 與 執行緒 B的讀操作 b 之間存在 happens-before 關系,盡管 a 操作和 b 操作在不同的執行緒中執行,但 JMM 向程式員保證 a 操作將對 b 操作可見),
happens-before 具體定義:前面一個操作的結果對后續操作是可見的,
1)如果一個操作 happens-before 另一個操作,那么第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前,
- 兩個操作之間存在 happens-before 關系,并不意味著一定要按照 happens-before 原則制定的順序來執行,只要兩個操作在指令重排序之后的執行結果與按照 happens-before 關系來執行的結果一致,那么這種重排序也是合法的,
happens-before 的 8 大原則:
- ① 單執行緒happen-before原則:同一個執行緒中前面的所有寫操作對后面的操作可見,
- ② 鎖的happen-before原則:同一個鎖的
unlock操作 happen-before 此鎖的lock操作,這條規則是指對一個物件的解鎖 happen-before 于后續對這個物件的加鎖, - ③ volatile的happen-before原則:對一個
volatile變數的寫操作 happen-before 對此變數的任意操作(當然也包括寫操作了), - ④ happen-before的傳遞性原則:如果 A 操作 happen-before B 操作,B 操作 happen-before C操作,那么 A 操作happen-before C 操作,
- ⑤ 執行緒啟動的happen-before原則:同一個執行緒的
start()方法 happen-before 于此執行緒的其它方法, - ⑥ 執行緒中斷的happen-before原則:執行緒 A 新寫入的所有變數,當呼叫
Thread.interrupt(),被打斷的執行緒 B,可以看到 A 的全部操作,- 理解:執行緒 A 寫入的所有變數,呼叫
Thread.interrupt(),被打斷的執行緒 B,可以看到 A 的全部操作,
- 理解:執行緒 A 寫入的所有變數,呼叫
- ⑦ 執行緒終結的happen-before原則:執行緒中的所有操作都 happen-before 執行緒的終止檢測,
- ⑧ 物件創建的happen-before原則:一個物件的初始化完成(建構式執行結束)先于他的
finalize()方法呼叫,
總結的面試題也挺費時間的,文章會不定時更新,有時候一天多更新幾篇,如果幫助您復習鞏固了知識點,還請三連支持一下,后續會億點點的更新!

為了幫助更多小白從零進階 Java 工程師,從CSDN官方那邊搞來了一套 《Java 工程師學習成長知識圖譜》,尺寸 870mm x 560mm,展開后有一張辦公桌大小,也可以折疊成一本書的尺寸,有興趣的小伙伴可以了解一下,當然,不管怎樣博主的文章一直都是免費的~

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/286761.html
標籤:java
下一篇:服務器專案部署總結(超詳細)
