Java內(nèi)存模型

概述

Java虛擬機(jī)規(guī)范試圖定義一種Java內(nèi)存模型(JMM)來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異踪栋,以實(shí)現(xiàn)讓Java程序在各種平臺下都能達(dá)到一致的內(nèi)存訪問效果刻伊。

主內(nèi)存和工作內(nèi)存

Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則搬男,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)腺兴。此處的變量與Java編程中所說的變量有所區(qū)別悼尾,它包括了實(shí)例字段傻铣、靜態(tài)字段和構(gòu)成數(shù)組對象的元素章贞,但不包括局部變量和方法參數(shù),因?yàn)楹笳呤蔷€程私有的非洲,不會被共享鸭限,自然就不存在競爭問題。

Java內(nèi)存模型規(guī)定了所有的變量都是存儲在主內(nèi)存中两踏。每條線程還有自己的工作內(nèi)存败京,線程的工作內(nèi)存中保存了該線程使用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行梦染,而不能直接讀寫主內(nèi)存中的變量赡麦。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成帕识。

image

這里所講的主內(nèi)存泛粹、工作內(nèi)存與Java堆、棧肮疗、方法區(qū)等并不是同一個(gè)層次的內(nèi)存劃分戚扳,這兩者基本上沒有任何關(guān)系。

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

Java內(nèi)存模型中定義了以下八種操作族吻,虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證下面提及的每一種操作都是原子的帽借、不可再分的(對于double和long類型的變量來說有例外):

  • lock(鎖定)
  • unlock(解鎖)
  • read(讀取)
  • load(載入)
  • use(使用)
  • assign(復(fù)制)
  • store(賦值)
  • write(寫入)

Java內(nèi)存模型還規(guī)定了在執(zhí)行上訴八種基本操作時(shí)必須滿足以下規(guī)則:

  1. 不允許read超歌、load砍艾、store 和 write 操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受巍举,或者從工作內(nèi)存發(fā)起了回寫但主內(nèi)存不接受的情況脆荷。
  2. 不允許一個(gè)線程丟棄它最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。
  3. 不允許一個(gè)線程無原因的把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中蜓谋。
  4. 一個(gè)新的變量只能在主內(nèi)存中誕生梦皮,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化的變量。
  5. 一個(gè)變量在同一時(shí)刻只允許一條線程對其進(jìn)行l(wèi)ock操作桃焕,但lock操作可以被同一條線程重復(fù)執(zhí)行多次剑肯,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作观堂,變量才會被解鎖让网。
  6. 如果對一個(gè)變量執(zhí)行l(wèi)ock操作,那么將會清空工作內(nèi)存中此變量的值师痕,在執(zhí)行引擎使用這個(gè)變量前溃睹,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
  7. 如果一個(gè)變量事先沒有被lock操作鎖定胰坟,那就不允許對它執(zhí)行unlock操作因篇,也不允許去unlock一個(gè)被其他線程鎖定住的變量。
  8. 對一個(gè)變量執(zhí)行unlock操作之前笔横,必須把此變量同步回主內(nèi)存中惜犀。

對于volatile型變量的特殊規(guī)則

當(dāng)一個(gè)變量定義為volatile之后,它將具備兩種特性:第一是保證此變量對所有線程的可見性狠裹,這里的可見性是指當(dāng)一條線程修改了這個(gè)變量的值,新值對于其他線程來說是可以立即得知的汽烦。而普通變量的值在線程間傳遞均需要通過主內(nèi)存來完成涛菠。

Java里面的運(yùn)算并非是原子操作,導(dǎo)致volatile變量的運(yùn)算在并發(fā)下一樣是不安全的撇吞。

由于volatile變量只能保證可見效俗冻,在不符合以下兩條規(guī)則的運(yùn)算場景下,我們?nèi)匀灰ㄟ^加鎖來保證原子性牍颈。

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

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

volatile 變量讀操作的性能消耗與普通變量幾乎沒有什么差別冶伞,但是寫操作則可能會慢一些,因?yàn)樗枰诒镜卮a中插入很多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行步氏。

對于long和double型變量的特殊規(guī)則

對于64位的數(shù)據(jù)類型响禽,在模型中定義了一種相對寬松的規(guī)定:允許虛擬機(jī)將沒有被volatile修飾的64位數(shù)據(jù)的讀寫操作劃分為兩次32位的操作來進(jìn)行,即可以不保證64位數(shù)據(jù)類型的操作的原子性。

但是目前虛擬機(jī)都幾乎把64位數(shù)據(jù)的讀寫操作作為原子操作來對待芋类,因此我們在編寫代碼中并不需要把long和double變量專門聲明為volatile隆嗅。

原子性、可見性與有序性

  • 原子性

    我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問讀寫是具備原子性的侯繁。如果應(yīng)用場景需要一個(gè)更大范圍的原子性保證胖喳,Java內(nèi)存模型還提供了lock和unlock操作來滿足這種需求,盡管虛擬機(jī)未把lock和unlock操作直接開放給用戶使用巫击,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式的使用這兩種操作禀晓,這兩個(gè)字節(jié)碼指令就對應(yīng)Java中的synchronized關(guān)鍵字。

  • 可見性

    可見性是指當(dāng)一個(gè)線程修改了共享變量的值坝锰,其他線程能夠立即得知這個(gè)修改粹懒。Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實(shí)現(xiàn)可見性顷级,無論是普通變量還是volatile變量都是如此凫乖,volatile變量的特殊規(guī)則是保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新弓颈。因此帽芽,可以說volatile變量保證了多線程操作時(shí)的可見性,而普通變量則不能保證這一點(diǎn)翔冀。

    除了volatile之外导街,Java還有兩個(gè)關(guān)鍵字能實(shí)現(xiàn)可見性,即synchronized和final纤子。同步塊的可見性是由“對變量執(zhí)行unlock操作之前搬瑰,必須先把此變量同步回主內(nèi)存中”這條規(guī)則獲得的,而final關(guān)鍵字的可見性是指:被final修飾的字段在構(gòu)造器中一旦初始化完成控硼,并且構(gòu)造器沒有把this引用傳遞出去泽论,那么其他線程中就能看見final字段的值。

  • 有序性

    如果在本線程內(nèi)觀察卡乾,所有的操作都是有序的翼悴;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無序的幔妨。前半句是指“線程內(nèi)表現(xiàn)為串行的語義(As - If - Serial)”鹦赎,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存和主內(nèi)存同步延遲”現(xiàn)象。

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

先行發(fā)生原則

先行發(fā)生是Java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系煞额。如果說操作A先行發(fā)生與B,那么操作A產(chǎn)生的影響能夠被B觀察到。下面是Java內(nèi)存模型中天然的先行發(fā)生關(guān)系:

  • 程序次序規(guī)則

    即程序的書寫順序膊毁。

  • 管程鎖定規(guī)則

    一個(gè)unlock操作先行發(fā)生與后面對同一個(gè)鎖的lock操作胀莹。

  • volatile變量規(guī)則

    對一個(gè)volatile變量的寫操作先行發(fā)生與后面對這個(gè)變量的讀操作。

  • 線程啟動規(guī)則

    Thread.start()方法先行發(fā)生與此線程的每一個(gè)動作婚温。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末描焰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子栅螟,更是在濱河造成了極大的恐慌荆秦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件力图,死亡現(xiàn)場離奇詭異步绸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吃媒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門瓤介,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赘那,你說我怎么就攤上這事刑桑。” “怎么了募舟?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵祠斧,是天一觀的道長。 經(jīng)常有香客問我拱礁,道長琢锋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任觅彰,我火速辦了婚禮,結(jié)果婚禮上钮热,老公的妹妹穿的比我還像新娘填抬。我一直安慰自己,他們只是感情好隧期,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布飒责。 她就那樣靜靜地躺著,像睡著了一般仆潮。 火紅的嫁衣襯著肌膚如雪宏蛉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天性置,我揣著相機(jī)與錄音拾并,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嗅义,可吹牛的內(nèi)容都是我干的屏歹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼之碗,長吁一口氣:“原來是場噩夢啊……” “哼蝙眶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起褪那,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤幽纷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后博敬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體友浸,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年冶忱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尾菇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡囚枪,死狀恐怖派诬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情链沼,我是刑警寧澤默赂,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站括勺,受9級特大地震影響缆八,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疾捍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一奈辰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乱豆,春花似錦奖恰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至揩尸,卻和暖如春蛹屿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背岩榆。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工错负, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坟瓢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓湿颅,卻偏偏與公主長得像载绿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子油航,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容