JMM與內(nèi)存可見(jiàn)性 Happens-Before原則

 如何保證內(nèi)存可見(jiàn)性?

  在java虛擬機(jī)的內(nèi)存模型中,有主內(nèi)存和工作內(nèi)存的概念,每個(gè)線程對(duì)應(yīng)一個(gè)工作內(nèi)存,并共享主內(nèi)存的數(shù)據(jù),下面看看操作普通變量和volatile變量有什么不同:

  1禁添、對(duì)于普通變量:讀操作會(huì)優(yōu)先讀取工作內(nèi)存的數(shù)據(jù),如果工作內(nèi)存中不存在桨踪,則從主內(nèi)存中拷貝一份數(shù)據(jù)到工作內(nèi)存中;寫(xiě)操作只會(huì)修改工作內(nèi)存的副本數(shù)據(jù)老翘,這種情況下,其它線程就無(wú)法讀取變量的最新值锻离。

  2铺峭、對(duì)于volatile變量,讀操作時(shí)JMM會(huì)把工作內(nèi)存中對(duì)應(yīng)的值設(shè)為無(wú)效汽纠,要求線程從主內(nèi)存中讀取數(shù)據(jù);寫(xiě)操作時(shí)JMM會(huì)把工作內(nèi)存中對(duì)應(yīng)的數(shù)據(jù)刷新到主內(nèi)存中卫键,這種情況下,其它線程就可以讀取變量的最新值虱朵。

什么是內(nèi)存屏障?

內(nèi)存屏障莉炉,又稱內(nèi)存柵欄,是一個(gè)CPU指令碴犬。在程序運(yùn)行時(shí)絮宁,為了提高執(zhí)行性能,編譯器和處理器會(huì)對(duì)指令進(jìn)行重排序服协,JMM為了保證在不同的編譯器和CPU上有相同的結(jié)果绍昂,通過(guò)插入特定類型的內(nèi)存屏障來(lái)禁止特定類型的編譯器重排序和處理器重排序,插入一條內(nèi)存屏障會(huì)告訴編譯器和CPU:不管什么指令都不能和這條Memory Barrier指令重排序蚯涮。

jmm是java內(nèi)存的訪問(wèn)模型

在程序運(yùn)行過(guò)程中治专,所有的變更會(huì)先在寄存器或本地cache中完成,然后才會(huì)被拷貝到主存以跨越內(nèi)存柵欄遭顶,此種跨越序列或順序稱為happens-before。

Happens-Before? Memory Model : 先行發(fā)生模型

定義:

第一個(gè)規(guī)則是為程序員提供的保證泪蔫,在同一個(gè)線程內(nèi)棒旗,前面的操作結(jié)果對(duì)后面的程序可見(jiàn)。?

第二個(gè)規(guī)則是向CPU提出的要求,在保證執(zhí)行結(jié)果相同的情況下铣揉,可以做程序的重排序饶深,來(lái)優(yōu)化程序的運(yùn)行。

由于指令重拍序,jmm需要提供:

原子性

可見(jiàn)性

有序性

happens-before原則規(guī)則:

程序次序規(guī)則:一個(gè)線程內(nèi)逛拱,按照代碼順序敌厘,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作;

鎖定規(guī)則:一個(gè)unLock操作先行發(fā)生于后面對(duì)同一個(gè)鎖額lock操作朽合;

volatile變量規(guī)則:對(duì)一個(gè)變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作俱两;

傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C曹步,則可以得出操作A先行發(fā)生于操作C宪彩;

線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每個(gè)一個(gè)動(dòng)作;

線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生讲婚;

線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè)尿孔,我們可以通過(guò)Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行筹麸;

對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的finalize()方法的開(kāi)始活合;

程序次序規(guī)則:一段代碼在單線程中執(zhí)行的結(jié)果是有序的。注意是執(zhí)行結(jié)果物赶,因?yàn)樘摂M機(jī)白指、處理器會(huì)對(duì)指令進(jìn)行重排序(重排序后面會(huì)詳細(xì)介紹)。雖然重排序了块差,但是并不會(huì)影響程序的執(zhí)行結(jié)果侵续,所以程序最終執(zhí)行的結(jié)果與順序執(zhí)行的結(jié)果是一致的。故而這個(gè)規(guī)則只對(duì)單線程有效憨闰,在多線程環(huán)境下無(wú)法保證正確性状蜗。

鎖定規(guī)則:這個(gè)規(guī)則比較好理解,無(wú)論是在單線程環(huán)境還是多線程環(huán)境鹉动,一個(gè)鎖處于被鎖定狀態(tài)轧坎,那么必須先執(zhí)行unlock操作后面才能進(jìn)行l(wèi)ock操作。

volatile變量規(guī)則:這是一條比較重要的規(guī)則泽示,它標(biāo)志著volatile保證了線程可見(jiàn)性缸血。通俗點(diǎn)講就是如果一個(gè)線程先去寫(xiě)一個(gè)volatile變量,然后一個(gè)線程去讀這個(gè)變量械筛,那么這個(gè)寫(xiě)操作一定是happens-before讀操作的捎泻。

傳遞規(guī)則:提現(xiàn)了happens-before原則具有傳遞性,即A happens-before B , B happens-before C埋哟,那么A happens-before C

線程啟動(dòng)規(guī)則:假定線程A在執(zhí)行過(guò)程中笆豁,通過(guò)執(zhí)行ThreadB.start()來(lái)啟動(dòng)線程B,那么線程A對(duì)共享變量的修改在接下來(lái)線程B開(kāi)始執(zhí)行后確保對(duì)線程B可見(jiàn)。

線程終結(jié)規(guī)則:假定線程A在執(zhí)行的過(guò)程中闯狱,通過(guò)制定ThreadB.join()等待線程B終止煞赢,那么線程B在終止之前對(duì)共享變量的修改在線程A等待返回后可見(jiàn)。

Java虛擬機(jī)內(nèi)存模型中定義了8種關(guān)于主內(nèi)存和工作內(nèi)存的交互協(xié)議操作:

lock:作用于主內(nèi)存的變量哄孤,把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占狀態(tài)照筑。

unlock:作用于主內(nèi)存的變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái)瘦陈,釋放后的變量可以被其他線程鎖定凝危。

read:作用于主內(nèi)的變量,把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中双饥,以便隨后的load動(dòng)作使用媒抠。

load:作用于工作內(nèi)存的變量,把read讀取操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量拷貝中咏花。

use:作用于工作內(nèi)存的變量趴生,把工作內(nèi)存中一個(gè)變量的值傳遞給java虛擬機(jī)執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行該操作昏翰。

assign:作用于工作內(nèi)存變量苍匆,把一個(gè)從執(zhí)行引擎接收到的變量的值賦值給工作變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼時(shí)將會(huì)執(zhí)行該操作棚菊。

store:作用于工作內(nèi)存的變量浸踩,把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用统求。

write:作用于主內(nèi)存的變量检碗,把store操作從工作內(nèi)存中得到的變量值放入主內(nèi)存的變量中。

內(nèi)存柵欄

主線程對(duì)done做了更改码邻,但是新的線程無(wú)法看到該變量值得變化(內(nèi)存柵欄)折剃,認(rèn)為done還是false。

volatile原語(yǔ)能夠向JIT發(fā)出警告像屋,done變量可能會(huì)被某個(gè)線程更改怕犁,對(duì)該變量的任何讀寫(xiě)都應(yīng)該忽視本地cache并直接對(duì)內(nèi)存進(jìn)行操作。

當(dāng)一個(gè)變量被聲明為volatile之后己莺,JMM對(duì)其做了特殊規(guī)則:

volatile變量的操作必須嚴(yán)格按load->use順序奏甫,前一個(gè)動(dòng)作是load時(shí)才能執(zhí)行use動(dòng)作,后一個(gè)動(dòng)作是use時(shí)才能執(zhí)行l(wèi)oad動(dòng)作凌受,即每次在工作內(nèi)存中使用變量前必須先從主內(nèi)存中刷新最新的值阵子,以保證能看到其他線程對(duì)變量的最新修改。

volatile變量的操作必須嚴(yán)格按assign->store順序胜蛉,前一個(gè)動(dòng)作是assign時(shí)才能執(zhí)行store動(dòng)作款筑,后一個(gè)動(dòng)作是store時(shí)才能執(zhí)行assign動(dòng)作智蝠,即每次在工作內(nèi)存為變量賦值之后必須將變量的值同步回主內(nèi)存腾么,以保證讓其他線程能看到變量的最新修改奈梳。

若線程對(duì)volatile變量V的assign或者use操作先于對(duì)volatile變量W的assign或者use操作,則線程對(duì)volatile變量A的read/load或者store/write操作也必定先于對(duì)volatile變量B的read/load或者store/write操作解虱。

在Java中包含了幾個(gè)關(guān)鍵字:volatile攘须、final和synchronized

synchronization

互斥,對(duì)于一個(gè)monitor對(duì)象殴泰,只能夠被一個(gè)線程持有

synchronization保證了線程在同步塊之前或者期間寫(xiě)入動(dòng)作于宙,對(duì)于后續(xù)進(jìn)入該代碼塊的線程是可見(jiàn)的,在一個(gè)線程退出同步塊時(shí),線程釋放monitor對(duì)象悍汛,它的作用是把CPU緩存數(shù)據(jù)(本地緩存數(shù)據(jù))刷新到主內(nèi)存中捞魁,從而實(shí)現(xiàn)該線程的行為可以被其它線程看到。在其它線程進(jìn)入到該代碼塊時(shí)离咐,需要獲得monitor對(duì)象,它在作用是使CPU緩存失效,從而使變量從主內(nèi)存中重新加載突委,然后就可以看到之前線程對(duì)該變量的修改瘟斜。

禁止指令的重排序,對(duì)于編譯器來(lái)說(shuō)术陶,同步塊中的代碼不會(huì)移動(dòng)到獲取和釋放monitor外面凑懂。

final?

正確的構(gòu)造一個(gè)對(duì)象后,final字段被設(shè)置后對(duì)于其它線程是可見(jiàn)的梧宫。正確構(gòu)造對(duì)象接谨,意思是在對(duì)象的構(gòu)造過(guò)程中,不允許對(duì)該對(duì)象進(jìn)行引用塘匣,不然的話脓豪,可能存在其它線程在對(duì)象還沒(méi)構(gòu)造完成時(shí)就對(duì)該對(duì)象進(jìn)行訪問(wèn),造成不必要的麻煩馆铁。

volatile

必須保證在被寫(xiě)入之后跑揉,會(huì)被刷新到主內(nèi)存中,這樣就可以立即對(duì)其它線程可以見(jiàn)

volatile禁止這兩個(gè)寫(xiě)入行為的重排序


內(nèi)存模型



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末埠巨,一起剝皮案震驚了整個(gè)濱河市历谍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辣垒,老刑警劉巖望侈,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異勋桶,居然都是意外死亡脱衙,警方通過(guò)查閱死者的電腦和手機(jī)侥猬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捐韩,“玉大人退唠,你說(shuō)我怎么就攤上這事』缧玻” “怎么了瞧预?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)仅政。 經(jīng)常有香客問(wèn)我垢油,道長(zhǎng),這世上最難降的妖魔是什么圆丹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任滩愁,我火速辦了婚禮,結(jié)果婚禮上辫封,老公的妹妹穿的比我還像新娘硝枉。我一直安慰自己,他們只是感情好秸讹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布檀咙。 她就那樣靜靜地躺著,像睡著了一般璃诀。 火紅的嫁衣襯著肌膚如雪弧可。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天劣欢,我揣著相機(jī)與錄音棕诵,去河邊找鬼。 笑死凿将,一個(gè)胖子當(dāng)著我的面吹牛校套,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牧抵,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼笛匙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了犀变?” 一聲冷哼從身側(cè)響起妹孙,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎获枝,沒(méi)想到半個(gè)月后蠢正,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡省店,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年嚣崭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笨触。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雹舀,死狀恐怖芦劣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情葱跋,我是刑警寧澤持寄,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站娱俺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏废麻。R本人自食惡果不足惜荠卷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烛愧。 院中可真熱鬧油宜,春花似錦、人聲如沸怜姿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)沧卢。三九已至蚁堤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間但狭,已是汗流浹背披诗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留立磁,地道東北人呈队。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像唱歧,于是被迫代替她去往敵國(guó)和親宪摧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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