Java 內(nèi)存模型
Java Memory Model , 為 java 內(nèi)存模型, 簡(jiǎn)稱為 JMM .
參考鏈接
主要參考為上述鏈接,上述講的特別好,很清楚,很詳細(xì)。
JMM 解決可見性的問題(同步包括獨(dú)占性和可見性)
Java 內(nèi)存模型的主要目的是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量。
兩個(gè)名詞含義:
主內(nèi)存: 線程之間共享變量存儲(chǔ)在主內(nèi)存中
工作內(nèi)存: 每個(gè)線程都有一個(gè)私有的本地內(nèi)存, 稱為工作內(nèi)存,里面存放著該線程以讀/寫共享變量的副本.
JMM 同時(shí)定義了線程和主內(nèi)存之間的抽象關(guān)系.
JMM通過控制主內(nèi)存與每個(gè)線程的本地內(nèi)存之間的交互, 提供了內(nèi)存可見性保證。-
Java線程間的通信由JMM控制JMM決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見 -
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存(與cache類比)線程對(duì)變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫內(nèi)存中的變量。
Java線程 <-----> 工作內(nèi)存 <------>sava和load操作 <-----> 主內(nèi)存
主內(nèi)存和工作內(nèi)存之間的具體交互協(xié)議
Java 內(nèi)存模型定義了一下八種操作:
-
lock: ----> 主內(nèi)存變量, 把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占狀態(tài) -
Unlock: ----> 主內(nèi)存變量, 釋放 -
read: ----> 主內(nèi)存變量, 把一個(gè)變量值從內(nèi)存?zhèn)魉偷骄€程的工作內(nèi)存 ,便于load操作 -
load: ----> 工作內(nèi)存的變量,把read的變量值放入到工作內(nèi)存的變量副本中 -
use: ----> 工作內(nèi)存的變量,工作內(nèi)存的變量值--> 執(zhí)行引擎 -
assign: ----> 工作內(nèi)存的變量, 把從執(zhí)行引擎得到的值----> 賦給工作內(nèi)存的變量 -
store: ----> 工作內(nèi)存的變量, 傳給主內(nèi)存,便于后面的write操作 -
write: ----> 主內(nèi)存的變量,把從工作內(nèi)存的傳過來的變量進(jìn)行write進(jìn)主內(nèi)存
Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,而沒有保證必須是連續(xù)執(zhí)行
happens-before 原則
在 JMM 中,如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另外一個(gè)操作可見,那么這兩個(gè)操作之間必需要存在 happens-before 關(guān)系.
上述兩個(gè)操作,可在同一個(gè)線程里,也可在不同的線程中.
規(guī)則如下:
-
程序順序規(guī)則:
一個(gè)線程中的每個(gè)操作,
happens- before作用于該線程中的任意后續(xù)操作. -
監(jiān)視器規(guī)則
對(duì)一個(gè)監(jiān)視器鎖的解鎖,
happens- before作用于隨后對(duì)這個(gè)監(jiān)視器鎖的加鎖. -
volatile變量原則對(duì)一個(gè)
volatile域的寫,happens- before作用于任意后續(xù)對(duì)這個(gè)volatile域的讀 -
傳遞性
如果 A happens- before B,且 B happens- before C,那么 A happens- before C.
對(duì) happens- before 的理解
兩個(gè)操作之間存在 happens- before, 并不是意味著 前一個(gè)操作必需在后一個(gè)操作執(zhí)行之前執(zhí)行, 而是僅僅要求前一個(gè)操作的結(jié)果(執(zhí)行后的結(jié)果),對(duì)后一個(gè)操作可見, 且前一個(gè)操作按順序排在第二個(gè)操作之前(代碼間存在重排序問題).
重排序
源代碼----> 編譯器優(yōu)化重排序 ----> 指令級(jí)并行重排序 -----> 內(nèi)存系統(tǒng)重排序 ----> 最終執(zhí)行的指令序列
為了保證內(nèi)存的可見性,Java編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來禁止特定類型的處理器重排序 , 因?yàn)橹嘏判蚩赡軙?huì)導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題。
JMM 特點(diǎn)
Java內(nèi)存模型JMM 確保在不同的編輯器和不同的處理器平臺(tái)上,通過禁止特定的的編譯器重排序和處理器重排序,為程序員提供一致的內(nèi)存可見性。
Java內(nèi)存模型JMM 不會(huì)破壞已有的
數(shù)據(jù)依賴性,as-if-serial 語義,程序順序規(guī)則. (具體含義見下面)
Java內(nèi)存模型把內(nèi)存屏障分為L(zhǎng)oadLoad、LoadStore、StoreLoad和StoreStore四種:

數(shù)據(jù)依賴性
編譯器和處理器在重排序時(shí),會(huì)遵守?cái)?shù)據(jù)依賴性。
- 數(shù)據(jù)依賴性僅針對(duì)單個(gè)處理器中執(zhí)行的指令序列和單個(gè)線程中執(zhí)行的操作,不同處理器之間和不同線程之間的數(shù)據(jù)依賴性不被編譯器和處理器考慮
as-if-serial 語義
不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執(zhí)行結(jié)果不能被改變。
編譯器,runtime 和處理器都必須遵守as-if-serial語義.
happens- before 程序順序規(guī)則
JMM僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見,且前一個(gè)操作按順序排在第二個(gè)操作之前
JMM允許不影響操作結(jié)果的重排序
重排序?qū)Χ嗑€程的影響
多線程程序中,對(duì)存在控制依賴的操作重排序,可能會(huì)改變程序的執(zhí)行結(jié)果。
單線程程序中, 對(duì)存在控制依賴的操作重排序,不會(huì)改變執(zhí)行結(jié)果。
數(shù)據(jù)競(jìng)爭(zhēng)與順序一致性保證
當(dāng)程序未正確同步時(shí),就會(huì)存在數(shù)據(jù)競(jìng)爭(zhēng)
在Java內(nèi)存模型對(duì)數(shù)據(jù)競(jìng)爭(zhēng)的定義如下:
- 在一個(gè)線程中寫一個(gè)變量
- 在另一個(gè)線程中讀同一個(gè)變量
- 寫和讀沒有通過同步來排序
如果一個(gè)多線程程序能正確同步,這個(gè)程序?qū)⑹且粋€(gè)沒有數(shù)據(jù)競(jìng)爭(zhēng)的程序,正確同步的多線程程序
JMM 對(duì)正確同步的多線程程序做了如下保證:
- 如果程序是正確同步的,則程序的執(zhí)行將具有順序一致性, 即程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同,
順序一致性內(nèi)存模型
順序一致性內(nèi)存模型 是一個(gè)被計(jì)算機(jī)科學(xué)家理想化了的理論參考模型,提供了極強(qiáng)的內(nèi)存可見性保證
與Java 內(nèi)存模型不同
特性:
- 一個(gè)線程中的所有操作必須按照程序的順序來執(zhí)行
- (不管程序是否同步)所有線程都只能看到一個(gè)單一的操作執(zhí)行順序
- 在順序一致性內(nèi)存模型中,每個(gè)操作都必須原子執(zhí)行且立刻對(duì)所有線程可見。
同步機(jī)制 :
volatile , synchronized, final
-
volatile關(guān)鍵字用
volatile修飾的變量,線程在每次使用變量的時(shí)候,都會(huì)讀取修改后的最新的值.對(duì)變量的寫操作,不依賴于當(dāng)前值
不能和其他變量同時(shí)出現(xiàn)在一個(gè)表達(dá)式中
volatile的作用就是使它修飾的變量的讀寫操作都必須在內(nèi)存中進(jìn)行
- 可見性:對(duì)一個(gè)
volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫入. - 原子性:對(duì)任意
volatile變量的讀/寫都具有原子性
volatile變量的寫-讀可以實(shí)現(xiàn)線程之間的通信, 如下:- 線程A寫一個(gè)volatile變量, 實(shí)質(zhì)上是線程A向接下來將要讀這個(gè)volatile變量的某個(gè)線程發(fā)出了(其對(duì)共享變量所在修改的)消息
- 線程B讀一個(gè)volatile變量,實(shí)質(zhì)上是線程B接收了之前某個(gè)線程發(fā)出的(在寫這個(gè)volatile變量之前對(duì)共享變量所做修改的)消息
- 線程A寫一個(gè)volatile變量,隨后線程B讀這個(gè)volatile變量,這個(gè)過程實(shí)質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息
volatile變量的寫-讀 的內(nèi)存意義當(dāng)寫一個(gè)
volatile變量時(shí), JMM 會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存;當(dāng)讀一個(gè)
volatile變量時(shí), JMM 會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無效,線程接下來將從主內(nèi)存中讀取共享變量.
為了達(dá)到上述的內(nèi)存意義效果,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序, 如下,采用保守策略的 JMM 內(nèi)存屏障插入策略:
- 在每個(gè)
volatile寫操作的前面插入一個(gè)StoreStore屏障 - 在每個(gè)
volatile寫操作的后面插入一個(gè)StoreLoad屏障 - 在每個(gè)
volatile讀操作的后面插入一個(gè)LoadLoad屏障 - 在每個(gè)
volatile讀操作的后面插入一個(gè)LoadStore屏障
- 可見性:對(duì)一個(gè)
-
synchronized關(guān)鍵字線程同步的內(nèi)部機(jī)制,通過加鎖的方式保證線程的可見性和互斥性,
即一個(gè)線程的執(zhí)行結(jié)果可以被另一個(gè)線程所看到且在同一時(shí)間只能有一個(gè)線程執(zhí)行被保護(hù)的代碼塊.鎖釋放和獲取的內(nèi)存語義:
當(dāng)線程釋放鎖時(shí),JMM 會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中
當(dāng)線程獲取鎖時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無效,從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須要從主內(nèi)存中去讀取共享變量
如下消息傳遞:
- 線程A釋放一個(gè)鎖,實(shí)質(zhì)上是線程A向接下來將要獲取這個(gè)鎖的某個(gè)線程發(fā)出了(線程A對(duì)共享變量所做修改的)消息;
- 線程B獲取一個(gè)鎖,實(shí)質(zhì)上是線程B接收了之前某個(gè)線程發(fā)出的(在釋放這個(gè)鎖之前對(duì)共享變量所做修改的)消息;
- 線程A釋放鎖,隨后線程B獲取這個(gè)鎖,這個(gè)過程實(shí)質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息
-
final關(guān)鍵字與前面的鎖和
volatile相比較,對(duì)final域的讀和寫更像是普通的變量訪問- 在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final域的寫入,與隨后把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序。
- 初次讀一個(gè)包含final域的對(duì)象的引用,與隨后初次讀這個(gè)final域,這兩個(gè)操作之間不能重排序
上面的兩條規(guī)則其實(shí)可分為
1.寫final域的重排序規(guī)則
- 讀final域的重排序規(guī)則
寫 final 域的重排序規(guī)則
JMM禁止編譯器把final域的寫重排序到構(gòu)造函數(shù)之外
編譯器會(huì)在 final 域的寫之后,構(gòu)造函數(shù) return 之前,插入一個(gè) StoreStore 屏障(內(nèi)存屏障)
這個(gè)屏障禁止處理器把 final 域的寫重排序到構(gòu)造函數(shù)之外。
讀 final 域的重排序規(guī)則
- 在一個(gè)線程中,初次讀對(duì)象引用與初次讀該對(duì)象包含的 final 域,JMM 禁止處理器重排序這兩個(gè)操作
僅針對(duì)與處理器(重排序的后兩種),
編譯器會(huì)在讀final域操作的前面插入一個(gè) LoadLoad 屏障
volatile與synchronized 的同異
-
volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器中的值是不確定的,需要從主存中讀取。 -
synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住. -
volatile僅能使用在變量級(jí)別,synchronized則可以使用在變量,方法. -
volatile僅能實(shí)現(xiàn)變量的修改可見性,但不具備原子特性,而synchronized則可以保證變量的修改可見性和原子性 -
volatile不會(huì)造成線程的阻塞,而synchronized可能會(huì)造成線程的阻塞. -
volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化,而synchronized標(biāo)記的變量可以被編譯器優(yōu)化.
如有錯(cuò)誤,請(qǐng)指出,謝謝.