2020-05-05

Java內(nèi)存模型(Java Memory Model,JMM)

1.主內(nèi)存與工作內(nèi)存

Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則袋马,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。此處的變量(Variables)與Java編程中所說的變量有所區(qū)別适篙,它包括了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但不包括局部變量與方法參數(shù)遗座,因?yàn)楹笳呤蔷€程私有的,不會(huì)被共享俊扳,自然就不會(huì)存在競爭問題途蒋。為了獲得較好的執(zhí)行效能,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來和主內(nèi)存進(jìn)行交互馋记,也沒有限制即時(shí)編譯器進(jìn)行調(diào)整代碼執(zhí)行順序這類優(yōu)化措施号坡。

Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中。每條線程還有自己的工作內(nèi)存梯醒,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝宽堆,線程對(duì)變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行茸习,而不能直接讀寫主內(nèi)存中的變量畜隶。不同的線程之間也無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成号胚,線程籽慢、主內(nèi)存、工作內(nèi)存三者的交互關(guān)系如圖所示猫胁。

image

2.內(nèi)存間交互操作

關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議箱亿,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存之類的實(shí)現(xiàn)細(xì)節(jié)弃秆,Java內(nèi)存模型中定義了以下8種操作來完成届惋,虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證下面提及的每一種操作都是原子的/不可再分的。

  • lock(鎖定):作用于主內(nèi)存的變量驾茴,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)盼樟。
  • unlock(解鎖):作用于主內(nèi)存的變量氢卡,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來锈至,釋放后的變量才可以被其他線程鎖定。
  • read(讀纫肭亍):作用于主內(nèi)存的變量峡捡,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存的變量副本中。
  • load(載入):作用于工作內(nèi)存的變量筑悴,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中们拙。
  • use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎阁吝,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作砚婆。
  • assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作装盯。
  • store(存儲(chǔ)):作用于工作內(nèi)存的變量坷虑,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用埂奈。
  • write(寫入):作用于主內(nèi)存的變量迄损,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。

如果要把一個(gè)變量從主內(nèi)存復(fù)制到工作內(nèi)存账磺,那就要順序的執(zhí)行read和load操作芹敌,如果要把變量從工作內(nèi)存同步回主內(nèi)存,就要順序的執(zhí)行store和write操作垮抗。注意氏捞,Java內(nèi)存模型只要求上述兩個(gè)操作必須按順序執(zhí)行,而沒有保證是連續(xù)執(zhí)行冒版。也就是說幌衣,read和load之間、store和write之間是可插入其他指令的壤玫,如對(duì)主內(nèi)存中的變量a豁护、b進(jìn)行訪問時(shí),一種可能出現(xiàn)的順序是read a欲间、read b楚里、load b、load a猎贴。除此之外班缎,Java內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時(shí)必須滿足如下規(guī)則:

  • 不允許read和load、store和write操作之一單獨(dú)出現(xiàn)她渴,即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受达址,或者從工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況出現(xiàn)。
  • 不允許一個(gè)線程丟棄它的最近的assign操作趁耗,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存沉唠。
  • 不允許一個(gè)線程無原因的(沒有發(fā)生任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。
  • 一個(gè)新的變量只能在主內(nèi)存中誕生苛败,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量满葛,換句話說,就是對(duì)一個(gè)變量實(shí)施use罢屈、store操作之前嘀韧,必須先執(zhí)行過來assign和load操作。
  • 一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作缠捌,但lock操作可以被同一條線程重復(fù)執(zhí)行多次锄贷,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖谊却。
  • 如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作蹂随,那就會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前因惭,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值岳锁。
  • 如果一個(gè)變量事先沒有被lock操作鎖定,那就不允許對(duì)它執(zhí)行unlock操作蹦魔,也不允許去unlock一個(gè)被其他線程鎖定住的變量激率。
  • 對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回到主內(nèi)存中(執(zhí)行store勿决、write操作)乒躺。

3.volatile型變量的特殊規(guī)則

當(dāng)一個(gè)變量定義為volatile之后,它將具備兩種特性低缩,第一是保證此變量對(duì)所有線程的可見性嘉冒,這里的可見性是指當(dāng)一條線程修改了這個(gè)變量的值,新值對(duì)于其他線程來說是可以立即得知的咆繁。而普通變量不能做到這一點(diǎn)讳推,普通變量的值在線程間傳遞均需要通過主內(nèi)存來完成。例如玩般,線程A修改一個(gè)普通變量的值银觅,然后向主內(nèi)存進(jìn)行回寫,另外一條線程B在線程A回寫完成之后再從主內(nèi)存進(jìn)行讀取操作坏为,新變量值才會(huì)對(duì)線程B可見究驴。

由于volatile變量只能保證可見性,在不符合以下兩條規(guī)則的運(yùn)算場景中匀伏,我們?nèi)匀灰ㄟ^加鎖(使用synchronized或java.util.concurrent中的原子類)來保證原子性洒忧。

  • 運(yùn)算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值够颠。
  • 變量不需要與其他的狀態(tài)變量共同參與不變約束熙侍。

使用volatile變量的第二個(gè)語義是禁止指令重排序優(yōu)化,普通的變量僅僅會(huì)保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果摧找,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致核行。因?yàn)樵谝粋€(gè)線程的方法執(zhí)行過程中無法感知到這點(diǎn),這就是Java內(nèi)存模式中描述的所謂的“線程內(nèi)表現(xiàn)為串行的語義”蹬耘。

4.long和double型變量的特殊規(guī)則

Java內(nèi)存模型要求lock、unlock减余、read综苔、load、assign、use如筛、store堡牡、write這8個(gè)操作都是原子性,但是對(duì)于64位的數(shù)據(jù)類型(long和double)杨刨,在模型中特別定義了一個(gè)相對(duì)寬松的規(guī)定:允許虛擬機(jī)將沒有被volatile修飾的64位數(shù)據(jù)的讀寫操作劃分為兩次32位的操作來進(jìn)行晤柄,即允許虛擬機(jī)實(shí)現(xiàn)選擇可以不保證64位數(shù)據(jù)類型的load、store妖胀、read和write這4個(gè)操作的原子性芥颈。

如果有多個(gè)線程共享一個(gè)并未聲明為volatile的long或double類型的變量,并且同時(shí)對(duì)它們進(jìn)行讀取和修改操作赚抡,那么某些線程可能會(huì)讀取到一個(gè)既非原值爬坑,也不是其他線程修改值的代表了“半個(gè)變量”的數(shù)值。

5.原子性涂臣、可見性與有序性

原子性:由Java內(nèi)存模型來直接保證的原子性變量操作包括read盾计、load、assign赁遗、use署辉、store和write,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問讀寫是具備原子性的岩四。

如果應(yīng)用場景需要一個(gè)更大范圍的原子性保證涨薪,Java內(nèi)存模型還提供了lock和unlock操作來滿足這種需求,盡管虛擬機(jī)未把lock和unlock操作直接開放給用戶使用炫乓,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式的使用這兩個(gè)操作刚夺,這兩個(gè)字節(jié)碼指令反映到Java代碼中就是同步塊——synchronized關(guān)鍵字,因此在synchronized塊之間的操作也具備原子性末捣。

可見性:可見性是指當(dāng)一個(gè)線程修改了共享變量的值侠姑,其他線程能夠立即得知這個(gè)修改。Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存箩做,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實(shí)現(xiàn)可見性莽红。volatile的特殊規(guī)則保證了新值能立即同步同主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新邦邦。因此安吁,volatile保證了多線程操作時(shí)變量的可見性。

除了volatile之外燃辖,Java還有兩個(gè)關(guān)鍵字能實(shí)現(xiàn)可見性鬼店,即synchronized和final。同步塊的可見性是由“對(duì)一個(gè)變量執(zhí)行unlock操作之前黔龟,必須先把此變量同步回主內(nèi)存中(執(zhí)行store妇智、write操作)”這條規(guī)則獲得的滥玷,而final關(guān)鍵字的可見性是指:被final修飾的字段在構(gòu)造器中一旦初始化完成,并且構(gòu)造器沒有把“this”的引用傳遞出去巍棱,那在其他線程中就能看見final字段的值惑畴。

有序性:Java程序中的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察,所有的操作都是有序的航徙;如果在一個(gè)線程中觀察另一個(gè)線程如贷,所有的操作都是無序的。前半句是指“線程內(nèi)表現(xiàn)為串行語義”到踏,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象杠袱。

Java語言提供了volatile和synchronized兩個(gè)關(guān)鍵字來保證線程之間操作的有序性,volatile關(guān)鍵字本身就包含了禁止指令重排序的語義夭禽,而synchronized則是由“一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的霞掺,這條規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行的進(jìn)入。

6.先行發(fā)生原則

先行發(fā)生是Java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系讹躯,如果說操作A先行發(fā)生于操作B菩彬,其實(shí)就是說在發(fā)生操作B之前,操作A產(chǎn)生的影響能被操作B觀察到潮梯,“影響”包括修改了內(nèi)存中共享變量的值骗灶、發(fā)送了消息、調(diào)用了方法等秉馏。下面是Java內(nèi)存模型下一些“天然的”先行發(fā)生關(guān)系耙旦,這些先行發(fā)生關(guān)系無須任何同步器協(xié)助就已經(jīng)存在,可以在編碼中直接使用萝究。

  • 程序次序規(guī)則:在一個(gè)線程內(nèi)免都,按照程序代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作帆竹。
  • 管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作绕娘。
  • volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。
  • 線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作栽连。
  • 線程終止規(guī)則:線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測险领,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值等手段檢測到線程已經(jīng)終止執(zhí)行秒紧。
  • 線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生绢陌,可以通過Thread.interrupted()方法檢測到是否有中斷發(fā)生。
  • 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于它的finalize()方法的開始熔恢。
  • 傳遞性:如果操作A先行發(fā)生于操作B脐湾,操作B先行發(fā)生于操作C,那就可以得出操作A先行發(fā)生于操作C的結(jié)論绩聘。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沥割,一起剝皮案震驚了整個(gè)濱河市耗啦,隨后出現(xiàn)的幾起案子凿菩,更是在濱河造成了極大的恐慌机杜,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衅谷,死亡現(xiàn)場離奇詭異椒拗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)获黔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門蚀苛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玷氏,你說我怎么就攤上這事堵未。” “怎么了盏触?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵渗蟹,是天一觀的道長。 經(jīng)常有香客問我赞辩,道長雌芽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任辨嗽,我火速辦了婚禮世落,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糟需。我一直安慰自己屉佳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布洲押。 她就那樣靜靜地躺著武花,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诅诱。 梳的紋絲不亂的頭發(fā)上髓堪,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音娘荡,去河邊找鬼干旁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛炮沐,可吹牛的內(nèi)容都是我干的争群。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼大年,長吁一口氣:“原來是場噩夢啊……” “哼换薄!你這毒婦竟也來了玉雾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤轻要,失蹤者是張志新(化名)和其女友劉穎复旬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冲泥,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驹碍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凡恍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片志秃。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嚼酝,靈堂內(nèi)的尸體忽然破棺而出浮还,到底是詐尸還是另有隱情,我是刑警寧澤闽巩,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布钧舌,位于F島的核電站,受9級(jí)特大地震影響又官,放射性物質(zhì)發(fā)生泄漏延刘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一六敬、第九天 我趴在偏房一處隱蔽的房頂上張望碘赖。 院中可真熱鬧,春花似錦外构、人聲如沸普泡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撼班。三九已至,卻和暖如春垒酬,著一層夾襖步出監(jiān)牢的瞬間砰嘁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來泰國打工勘究, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矮湘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓口糕,卻偏偏與公主長得像缅阳,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子景描,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361