枯燥的Java并發(fā) - volatile

volatile是一個(gè)特征修飾符,它的的作用是作為指令關(guān)鍵字茅诱,確保本條指令不會(huì)因編譯器優(yōu)化而省略逗物,且要求每次直接讀取最新值。

JMM規(guī)范介紹

Java內(nèi)存模型(Java Memory Model簡(jiǎn)稱JMM)是一種抽象的概念瑟俭,并不真實(shí)存在翎卓,它描述的是一組規(guī)則或規(guī)范,通過(guò)這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段摆寄,靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問(wèn)方式失暴。JVM運(yùn)行程序的實(shí)體是線程,而每個(gè)線程創(chuàng)建時(shí)JVM都會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(有些地方稱為椡钟空間)锐帜,用于存儲(chǔ)線程私有的數(shù)據(jù),而Java內(nèi)存模型中規(guī)定所有變量都存儲(chǔ)在主內(nèi)存畜号,主內(nèi)存是共享內(nèi)存區(qū)域缴阎,所有線程都可以訪問(wèn),但線程對(duì)變量的操作(讀取賦值等)必須在工作內(nèi)存中進(jìn)行简软,首先要將變量從主內(nèi)存拷貝的自己的工作內(nèi)存空間蛮拔,然后對(duì)變量進(jìn)行操作,操作完成后再將變量寫回主內(nèi)存痹升,不能直接操作主內(nèi)存中的變量建炫,工作內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝。因此疼蛾,不同的線程間無(wú)法訪問(wèn)對(duì)方的工作內(nèi)存肛跌,線程間的通信(傳值)必須通過(guò)主內(nèi)存來(lái)完成。

線程,工作內(nèi)存衍慎,主內(nèi)存工作交互圖(基于JMM規(guī)范):


image.png

JMM與JVM內(nèi)存區(qū)域的劃分是不同的概念層次转唉,更恰當(dāng)?shù)恼f(shuō)JMM描述的是一組規(guī)則,通過(guò)這組規(guī)則控制程序中各個(gè)變量在共享數(shù)據(jù)區(qū)域和私有數(shù)據(jù)區(qū)域的訪問(wèn)方式稳捆,JMM是圍繞原子性赠法,有序性、可見性展開乔夯。JMM與Java內(nèi)存區(qū)域唯一相似點(diǎn)砖织,都存在共享數(shù)據(jù)區(qū)域和私有數(shù)據(jù)區(qū)域,在JMM中主內(nèi)存屬于共享數(shù)據(jù)區(qū)域末荐,從某個(gè)程度上講應(yīng)該包括了堆和方法區(qū)侧纯,而工作內(nèi)存數(shù)據(jù)線程私有數(shù)據(jù)區(qū)域,從某個(gè)程度上講則應(yīng)該包括程序計(jì)數(shù)器鞠评、虛擬機(jī)棧以及本地方法棧茂蚓。

通過(guò)上面對(duì)JMM的介紹,我們知道了各個(gè)線程會(huì)將共享變量從主內(nèi)存中拷貝到工作內(nèi)存剃幌,然后執(zhí)行引擎會(huì)基于工作內(nèi)存中的數(shù)據(jù)進(jìn)行操作處理。線程在工作內(nèi)存進(jìn)行操作后何時(shí)會(huì)寫到主內(nèi)存中晾浴?這個(gè)時(shí)機(jī)對(duì)普通變量是沒(méi)有規(guī)定的负乡,而針對(duì)volatile修飾的變量給java虛擬機(jī)特殊的約定,線程對(duì)volatile變量的修改會(huì)立刻被其他線程所感知脊凰,即不會(huì)出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象抖棘,從而保證數(shù)據(jù)的“可見性”。

volatile 的特性

  • 保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性狸涌,即一個(gè)線程修改了某個(gè)變量的值切省,這新值對(duì)其他線程來(lái)說(shuō)是立即可見的。(實(shí)現(xiàn)可見性)
  • 禁止進(jìn)行指令重排序帕胆。(實(shí)現(xiàn)有序性)
  • volatile 只能保證對(duì)單次讀/寫的原子性朝捆。i++ 這種操作不能保證原子性。

volatile 的實(shí)現(xiàn)原理

可見性

為了提高處理速度懒豹,處理器不直接和內(nèi)存進(jìn)行通信芙盘,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進(jìn)行操作脸秽,但操作完不知道何時(shí)會(huì)寫到內(nèi)存儒老。如果對(duì)聲明了 volatile的變量進(jìn)行寫操作,JVM就會(huì)向處理器發(fā)送一條Lock前綴的指令记餐,將這個(gè)變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存驮樊。但是,就算寫回到內(nèi)存,如果其他處理器緩存的值還是舊的囚衔,再執(zhí)行計(jì)算操作就會(huì)有問(wèn)題挖腰。所以,在多處理器下佳魔,為了保證各個(gè)處理器的緩存是一致的曙聂,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議(MESI),每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了鞠鲜,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改宁脊,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候贤姆,會(huì)重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存里榆苞。

因此,經(jīng)過(guò)分析我們可以得出如下結(jié)論:

  1. Lock前綴的指令會(huì)引起處理器緩存寫回內(nèi)存霞捡;
  2. 一個(gè)處理器的緩存回寫到內(nèi)存會(huì)導(dǎo)致其他處理器的緩存失效坐漏;
  3. 當(dāng)處理器發(fā)現(xiàn)本地緩存失效后,就會(huì)從內(nèi)存中重讀該變量數(shù)據(jù)碧信,即可以獲取當(dāng)前最新值赊琳。

這樣針對(duì)volatile變量通過(guò)這樣的機(jī)制就使得每個(gè)線程都能獲得該變量的最新值。

線程操作數(shù)據(jù)流程圖


image.png
有序性

Java內(nèi)存模型具備一些先天的“有序性”砰碴,即不需要通過(guò)任何手段就能夠得到保證的有序性躏筏,這個(gè)通常也稱為happens-before 原則。如果兩個(gè)操作的執(zhí)行次序無(wú)法從happens-before原則推導(dǎo)出來(lái)呈枉,那么它們就不能保證它們的有序性趁尼,虛擬機(jī)可以隨意地對(duì)它們進(jìn)行重排序。


image.png

指令重排序:JVM能根據(jù)處理器特性(CPU多級(jí)緩存系統(tǒng)猖辫、多核處理器等)適當(dāng)?shù)膶?duì)機(jī)器指令進(jìn)行重排序酥泞,使機(jī)器指令能更符合CPU的執(zhí)行特性,最大限度的發(fā)揮機(jī)器性能啃憎。

JVM中提供了四類內(nèi)存屏障來(lái)禁止指令重排優(yōu)化

屏障類型 指令示例 說(shuō)明
LoadLoad Load1; LoadLoad; Load2 保證load1的讀取操作在load2及后續(xù)讀取操作之前執(zhí)行
StoreStore Store1; StoreStore; Store2 在store2及其后的寫操作執(zhí)行前芝囤,保證store1的寫操作已刷新到主內(nèi)存
LoadStore Load1; LoadStore; Store2 在stroe2及其后的寫操作執(zhí)行前,保證load1的讀操作已讀取結(jié)束
StoreLoad Store1; StoreLoad; Load2 保證store1的寫操作已刷新到主內(nèi)存之后荧飞,load2及其后的讀操作才能執(zhí)行

內(nèi)存屏障凡人,又稱內(nèi)存柵欄,是一個(gè)CPU指令叹阔,它的作用有兩個(gè)挠轴,一是保證特定操作的執(zhí)行順序,二是保證某些變量的內(nèi)存可見性(利用該特性實(shí)現(xiàn)volatile的內(nèi)存可見性)耳幢。由于編譯器和處理器都能執(zhí)行指令重排優(yōu)化岸晦。如果在指令間插入一條Memory Barrier則會(huì)告訴編譯器和CPU欧啤,不管什么指令都不能和這條Memory Barrier指令重排序,也就是說(shuō)通過(guò)插入內(nèi)存屏障禁止在內(nèi)存屏障前后的指令執(zhí)行重排序優(yōu)化启上。Memory Barrier的另外一個(gè)作用是強(qiáng)制刷出各種CPU的緩存數(shù)據(jù)邢隧,因此任何CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本「栽冢總之倒慧,volatile變量正是通過(guò)內(nèi)存屏障實(shí)現(xiàn)其在內(nèi)存中的語(yǔ)義,即可見性和禁止重排優(yōu)化包券。

JMM針對(duì)編譯器制定的volatile重排序規(guī)則表

第一個(gè)操作 第二個(gè)操作:普通讀寫 第二個(gè)操作:volatile讀 第二個(gè)操作:volatile寫
普通讀寫 可以重排 可以重排 不可以重排
volatile讀 不可以重排 不可以重排 不可以重排
volatile寫 可以重排 不可以重排 不可以重排

volatile寫插入內(nèi)存屏障后生成的指令序列示意圖


image.png

volatile讀插入內(nèi)存屏障后生成的指令序列示意圖


image.png
原子性

CPU緩存一致性協(xié)議(MESI)為了保證高效緩存的數(shù)據(jù)一致性纫谅,它將讀到的每個(gè)緩存行(cache line 一般為64Bytes)使用4種狀態(tài)進(jìn)行標(biāo)記(使用額外的兩位(bit)表示),分別是修改(modify)溅固、獨(dú)占(exclusive)付秕、共享(shared)、失效(invalid)侍郭。

image.png

雖然有MESI協(xié)議可以保證緩存一致性询吴,但是如果有一個(gè)線程在正要進(jìn)行+1的時(shí)候被掛起了,而另一個(gè)線程則正好執(zhí)行完了x+=1的操作亮元,此時(shí)回到第一個(gè)線程繼續(xù)執(zhí)行(該變量已經(jīng)被寄存器讀取了猛计,此時(shí)并不會(huì)再次讀取內(nèi)存),這樣就會(huì)導(dǎo)致一個(gè)錯(cuò)誤的數(shù)據(jù)爆捞。因此有滑,volatile只能保證內(nèi)存的可見性,無(wú)法保證原子性的問(wèn)題嵌削。
所以在多線程中的單例模式都會(huì)在獲取實(shí)例的方法上加上一個(gè)synchronized關(guān)鍵字,以確保只會(huì)生成一個(gè)對(duì)象望艺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苛秕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子找默,更是在濱河造成了極大的恐慌艇劫,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惩激,死亡現(xiàn)場(chǎng)離奇詭異店煞,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)风钻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門顷蟀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人骡技,你說(shuō)我怎么就攤上這事鸣个⌒叻矗” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵囤萤,是天一觀的道長(zhǎng)昼窗。 經(jīng)常有香客問(wèn)我,道長(zhǎng)涛舍,這世上最難降的妖魔是什么澄惊? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮富雅,結(jié)果婚禮上掸驱,老公的妹妹穿的比我還像新娘。我一直安慰自己吹榴,他們只是感情好亭敢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著图筹,像睡著了一般帅刀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上远剩,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天扣溺,我揣著相機(jī)與錄音,去河邊找鬼瓜晤。 笑死锥余,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痢掠。 我是一名探鬼主播驱犹,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼足画!你這毒婦竟也來(lái)了雄驹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤淹辞,失蹤者是張志新(化名)和其女友劉穎医舆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體象缀,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔬将,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了央星。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霞怀。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖等曼,靈堂內(nèi)的尸體忽然破棺而出里烦,到底是詐尸還是另有隱情凿蒜,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布胁黑,位于F島的核電站废封,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丧蘸。R本人自食惡果不足惜漂洋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望力喷。 院中可真熱鬧刽漂,春花似錦、人聲如沸弟孟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拂募。三九已至庭猩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陈症,已是汗流浹背蔼水。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留录肯,地道東北人趴腋。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像论咏,于是被迫代替她去往敵國(guó)和親优炬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354