Java并發(fā)系列之volatile

講到Java并發(fā)喧笔,多線程編程溃斋,一定避免不了對關(guān)鍵字volatile的了解吸申,那么如何來認識volatile,從哪些方面來了解它會比較合適呢截碴?

個人認為,既然是多線程編程走哺,那我們在平常的學習中哲虾,工作中,大部分都接觸到的就是線程安全的概念晒旅。

而線程安全就會涉及到共享變量的概念汪诉,所以首先,我們得弄清楚共享變量是什么拟烫,且處理器和內(nèi)存間的數(shù)據(jù)交互機制是如何導致共享變量變得不安全迄本。

共享變量

能夠在多個線程間被多個線程都訪問到的變量岸梨,我們稱之為共享變量。共享變量包括所有的實例變量曹阔,靜態(tài)變量和數(shù)組元素。他們都被存放在堆內(nèi)存中寂拆。

處理器與內(nèi)存的通信機制

大家都知道處理器是用來做計算的抓韩,且速度是非常快的尝江,而內(nèi)存是用來存儲數(shù)據(jù)的英上,且其訪問速度相比處理器來說,是慢了好幾個級別的惭聂。那么當處理器需要處理數(shù)據(jù)時相恃,如果每次都直接從內(nèi)存拿數(shù)據(jù)的話,就會導致效率非常低耕腾,因此在現(xiàn)代計算機系統(tǒng)中杀糯,處理器是不直接跟內(nèi)存通信的,而是在處理器和內(nèi)存之間設置了多個緩存火脉,也就是我們常常聽到的L1, L2, L3等高速緩存倦挂。

具體架構(gòu)如下所示:


memory_processor_communication.png

處理器都是將數(shù)據(jù)從內(nèi)存讀到自己內(nèi)部的緩存中,然后在緩存中對數(shù)據(jù)進行修改等操作没炒,結(jié)束后再由緩存寫到回主存中去犯戏。
如果一個共享變量 X,在多線程的情況下种吸,同時被多個處理器讀到各自的緩存中去呀非,當其中一個處理器修改了X的值,改成Y了岸裙,先寫回了內(nèi)存降允,而此時另外一個處理器,又將X改成Z剧董,再寫回內(nèi)存,那么之前的Y就會被覆蓋掉了侠草。

這種情況下犁嗅,數(shù)據(jù)就已經(jīng)有問題了,這種因為多線程操作而導致的異常問題功蜓,通常我們就叫做線程不安全宠蚂。

memory_processor_communication_core1.png

memory_processor_communication_core2.png

如上述兩圖所示求厕,X的變量同時被不同的處理器修改成各自的Y和Z扰楼,那么如何避免這種情況呢弦赖?
這就涉及到了Java內(nèi)存模型中的可見性的概念浦辨。

Java內(nèi)存模型之可見性

可見性,意思就是說流酬,在多線程編程中芽腾,某個共享變量在其中一個線程被修改了,其修改結(jié)果要馬上能夠被其他線程看到晦嵌,拿上面的例子來說,也就是當X在其中一個處理器的緩存中被修改成Y了旱函, 另一個處理器必須能夠馬上知道自己緩存中的X已經(jīng)被修改成Y了描滔,當此處理器要拿此變量去參與計算的時候,必須重新去內(nèi)存中將此變量的值Y讀到緩存中券腔。

而一個變量拘泞,如果被聲明成violate,那么其就能保證這種可見性辱魁,這就是volatile變量的作用了诗鸭。

volatile

那么 volatile 變量能夠保證可見性的實現(xiàn)原理是什么?
聲明成volatile的變量强岸,在編譯成匯編指令的時候蝌箍,會多出以下一行:

0x0bca13ae:lock addl $0x0,(%esp)      ;

這一句指令的意思是在寄存器上做一個+0的空操作暴心,但這條指令有個Lock前綴杂拨。
而處理器在處理Lock前綴指令時,其實是聲言了處理器的Lock#信號。
在之前的處理器中甚亭,Lock#信號會導致傳輸數(shù)據(jù)的總線被鎖定亏狰,其他處理器都不能訪問總線,從而保證處理Lock指令的處理器能夠獨享操作數(shù)據(jù)所在的內(nèi)存區(qū)域暇唾。

但由于總線被鎖住,其他的處理器都被堵住了瘸味,影響多處理器執(zhí)行的效率够挂。在后來的處理器中,聲言Lock#信號的處理器枯冈,不會再鎖住總線办悟,而是檢查到數(shù)據(jù)所在的內(nèi)存區(qū)域,如果是在處理器的內(nèi)部緩存中炫加,則會鎖定此緩存區(qū)域钠右,將緩存寫回到內(nèi)存當中看疗,并利用緩存一致性的原則來保證其他處理器中的緩存區(qū)域數(shù)據(jù)的一致性兼贸。

緩存一致性

緩存一致性原則會保證一個在緩存中的數(shù)據(jù)被修改了饥脑,會保證其他緩存了此數(shù)據(jù)的處理器中的緩存失效柬甥,從而讓處理器重新去內(nèi)存中讀取最新修改后的數(shù)據(jù)。

在實際的處理器操作中苛蒲,各個處理器會一直在總線上嗅探其內(nèi)部緩存區(qū)域中的內(nèi)存地址在其它處理器的操作情況臂外,一旦嗅探到某處理器打算修改某內(nèi)存地址,而此內(nèi)存地址剛好也在自己內(nèi)部的緩存中漏健,則會強制讓自己的緩存無效蔫浆。當下次訪問此內(nèi)存地址的時候,則重新從內(nèi)存當中讀取新數(shù)據(jù)瓦盛。

volatile不僅保證了共享變量在多線程間的可見性原环,其還保證了一定的有序性。

有序性

何謂有序性呢扮念?
事實上柜与,java程序代碼在編譯器階段和處理器執(zhí)行階段,為了優(yōu)化執(zhí)行的效率弄匕,有可能會對指令進行重排序。
如果一些指令彼此之間互相不影響剩瓶,那么就有可能不按照代碼順序執(zhí)行城丧,比如后面的代碼先執(zhí)行亡哄,而之前的代碼則慢執(zhí)行,但處理器會保證結(jié)束時的輸出結(jié)果是一致的。
以上的這種情況就說明指令有可能不是有序的灵临。

volatile變量趴荸,上面我們看過其匯編指令,會多出一條Lock前綴的指令顿涣,這條指令能夠 保證酝豪,在這條指令之前的所有指令全部執(zhí)行完畢,而在這條指令之后的所有指令全部未執(zhí)行,也相于在這里立起了一道柵欄锄码,稱之為內(nèi)存柵欄滋捶,而更通俗的說法,則是內(nèi)存屏障重窟。

那么有了這道屏障巡扇,volatile變量就禁止了指令的重排序,從而保證了指令執(zhí)行的有序性厅翔。

所有對volatile變量的讀操作一定發(fā)生在對volatile變量的寫操作之后刀闷。這同時也說明了volatile變量在多個線程之間能夠?qū)崿F(xiàn)可見性的原理。所以各種規(guī)定和操作顽分,其實之間互有關(guān)聯(lián)施蜜,彼此依賴,才能更好地保證指令執(zhí)行的準確和效率花墩。

內(nèi)存屏障

在上面我們也引出了內(nèi)存屏障的概念悬秉,也知道了澄步,其實它就是一組處理器的操作指令。

插入一個內(nèi)存屏障和泌,則相當于告訴處理器和編譯器先于這個指令的必須先執(zhí)行村缸,后于這個指令的必須后執(zhí)行。

image

內(nèi)存屏障另一個作用是強制更新一次不同CPU的緩存武氓。

例如梯皿,一個寫屏障會把這個屏障前寫入的數(shù)據(jù)刷新到緩存,這樣任何試圖讀取該數(shù)據(jù)的線程將得到最新值县恕,而不用考慮到底是被哪個cpu核心或者哪顆CPU執(zhí)行的东羹。

這再仔細一想忠烛,不就是上面所說的volatile的作用嗎属提?

所以,內(nèi)存屏障美尸,可見性冤议,有序性,緩存一致性原則师坎,在java并發(fā)中各種各樣的名詞恕酸,本質(zhì)上可能就只是同一種現(xiàn)象或者同一種設計,從不同的角度觀察和探討所得出的不同的解釋胯陋。
下一篇文章
Java并發(fā)系列之synchronized

參考資料

聊聊并發(fā)(一)深入分析Volatile的實現(xiàn)原理

內(nèi)存屏障與JVM并發(fā)

Java并發(fā)編程:volatile關(guān)鍵字解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蕊温,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子遏乔,更是在濱河造成了極大的恐慌义矛,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盟萨,死亡現(xiàn)場離奇詭異症革,居然都是意外死亡,警方通過查閱死者的電腦和手機鸯旁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門噪矛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铺罢,你說我怎么就攤上這事艇挨。” “怎么了韭赘?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵缩滨,是天一觀的道長。 經(jīng)常有香客問我,道長脉漏,這世上最難降的妖魔是什么苞冯? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮侧巨,結(jié)果婚禮上舅锄,老公的妹妹穿的比我還像新娘。我一直安慰自己司忱,他們只是感情好皇忿,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坦仍,像睡著了一般鳍烁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上繁扎,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天幔荒,我揣著相機與錄音,去河邊找鬼梳玫。 笑死铺峭,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的汽纠。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼傀履,長吁一口氣:“原來是場噩夢啊……” “哼虱朵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钓账,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤碴犬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后梆暮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體服协,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年啦粹,在試婚紗的時候發(fā)現(xiàn)自己被綠了偿荷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡唠椭,死狀恐怖跳纳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贪嫂,我是刑警寧澤寺庄,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響斗塘,放射性物質(zhì)發(fā)生泄漏赢织。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一馍盟、第九天 我趴在偏房一處隱蔽的房頂上張望于置。 院中可真熱鬧,春花似錦朽合、人聲如沸俱两。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宪彩。三九已至,卻和暖如春讲婚,著一層夾襖步出監(jiān)牢的瞬間尿孔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工筹麸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留活合,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓物赶,卻偏偏與公主長得像白指,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子酵紫,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

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