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

volatile字面有“易揮發(fā)”的意思,引申開來就是有“不穩(wěn)定”的意思梆暖。volatile?關(guān)鍵字用于修飾共享可變變量伞访,即沒有使用?final?關(guān)鍵字修飾的實(shí)例變量或靜態(tài)變量,相應(yīng)的變量就被稱為?volatile?變量轰驳。

volatile關(guān)鍵字表示被修飾的變量的值容易變化(?即被其他線程更改?)厚掷。因而不穩(wěn)定。

volatile變量的不穩(wěn)定性意味著對這種變量的讀和寫操作都必須從高速緩存或者主內(nèi)存级解。(也是通過高速緩存讀让昂凇)中讀取,以讀取變量的相對新值勤哗。因此抡爹,volatile變量不會被編譯器分配到寄存器進(jìn)行存儲,對?volatile?變量的讀寫操作都是內(nèi)存訪問(?訪問高速緩存相當(dāng)于主內(nèi)存?)操作俺陋。

volatile關(guān)鍵字常被稱為輕量級鎖?豁延,其作用與鎖的作用有相同的地方:保證可見性和有序性。所不同的是腊状,在原子性方面它僅能保障寫?volatile?變量操作的原子性诱咏,但沒有鎖的排他性;其次缴挖,volatile?關(guān)鍵字的使用不會引起上下文切換(?這是?volatile?被冠以?“輕量級”?的原因?)袋狞。因此,volatile?更像是一個(gè)輕量級簡易(?功能比鎖有限?)鎖映屋。

volatile的作用

volatile關(guān)鍵字的作用包括:保障可見性苟鸯、保障有序性和保障?long/double?型變量讀寫?操作的原子性。volatile?關(guān)鍵宇能夠保障對?long/double?型變量的寫操作具有原子性棚点。在?Java?語言中早处,?對?long?型和?double?型以外的任何類型的變量的寫操作都是原子操作√蔽觯考慮到某些?32?位?Java?虛擬機(jī)上對?long/double?型變量進(jìn)行的寫操作可能不具有原子性砌梆,Java?語言規(guī)范特別地規(guī)定對?long/double?型?volatile?變量的寫操作和讀操作也具有原子性。

但是贬循,volatile僅僅保障對其修飾的變量的寫操作(?以及讀操作?)本身的原子性?咸包,而這并不表示對?volatile?變量的賦值操作一定具有原子性。例如杖虾,如下對volatile?變量?count1的賦值操作并不是原子操作:

count1?=?count2??+?1;

如果變量count2也是一個(gè)共享變量烂瘫,那么該賦值操作實(shí)際上是一個(gè)read-modify-write?操作。其執(zhí)行過程中其他線程可能已經(jīng)更新了?count2?的值奇适,因此該操作不具備不可分割性坟比,也就不是原子操作芦鳍。如果變量count2?是一個(gè)局部變量,那么該賦值操作就是一個(gè)原子操作温算。

一般而言怜校,對volatile變量的賦值操作间影,其右邊表達(dá)式中只要涉及共享變量?(?包括被賦值的?volatile?變量本身?)注竿,那么這個(gè)賦值操作就不是原子操作。要保障這樣操作的原子性魂贬,?我們?nèi)匀恍枰柚i巩割。

寫線程對volatile變量的寫操作會產(chǎn)生類似于釋放鎖的效果。讀線程對?volatile?變量的讀操作會產(chǎn)生類似于獲得鎖的效果付燥。因此宣谈,volatile具有保障有序性和可見性的作用。

對于volatile變量的寫操作键科,Java?虛擬機(jī)會在該操作之前插入一個(gè)釋放屏障闻丑,并在該?操作之后插入一個(gè)存儲屏障,如圖所示勋颖。

其中嗦嗡,釋放屏障禁止了volatile寫操作與該操作之前的任何讀、寫操作進(jìn)行重排序饭玲,?從而保證了?volatile?寫操作之前的任何讀侥祭、寫操作會先于?volatile?寫操作被提交,即其他線程看到寫線程對?volatile?變量的更新時(shí)茄厘,寫線程在更新?volatile?變量之前所執(zhí)行的內(nèi)存操作的結(jié)果對于讀線程必然也是可見的矮冬。這就保障了讀線程對寫線程在更新?volatile?變量前對共享變量所執(zhí)行的更新操作的感知順序與相應(yīng)的源代碼順序一致,即保障了有序性次哈。

volatile雖然能夠保障有序性胎署,但是它不像鎖那樣具備排他性,所以并不能保障其他操作的原子性窑滞,而只能夠保障對被修飾變量的寫操作的原子性琼牧。因此,volatile?變量寫操作之前的操作如果涉及共享可變變量葛假,那么競態(tài)仍可能產(chǎn)生障陶。這是因?yàn)楣蚕碜兞勘毁x值給?volatile?變量的時(shí)候其他線程可能已經(jīng)更新了該共享變量的值。

存儲屏障具有沖刷處理器緩存的作用聊训,因此在volatile變量寫操作之后插入的一個(gè)存儲屏障就使得該存儲屏障前所有操作的結(jié)果(?包括?volatile?變量寫操作及該操作之前的任何操作?)對其他處理器來說是可同步的抱究。

對于volatile變量讀操作,Java虛擬機(jī)會在該操作之前插入一個(gè)加載屏障?(?Load?Barrier?)带斑,并在該操作之后插入一個(gè)獲取屏障(Acquire?Barrier)鼓寺,如圖所示勋拟。

其中,加載屏障通過刷新處理器緩存妈候,使其執(zhí)行線程(讀線程)所在的處理器將其他處理器對共享變量(可能是多個(gè)變量)所做的更新同步到該處理器的高速緩存中敢靡。讀線程執(zhí)行的加載屏障和寫線程執(zhí)行的存儲屏障配合在一起使得寫線程對volatile變量的寫操作以及在此之前所執(zhí)行的其他內(nèi)存操作的結(jié)果對讀線程可見即保障了可見性。因此苦银,volatile?不僅僅保障了?volatile?變量本身的可見性啸胧,還保障了寫線程在更新?volatile??變量之前執(zhí)行的所有操作的結(jié)果對讀線程可見。這種可見性保障類似于鎖對可見性的保障幔虏,與鎖不同的是?volatile不具備排他性?纺念,因而它不能保障讀線程讀取到的這些共享變量的值是最新的,即讀線程讀取到這些共享變量的那一刻可能已經(jīng)有其他寫線程更新了這些共享變量的值想括。另外陷谱,獲取屏障禁止了?volatile?讀操作之后的任何讀、寫操作與?volatile?讀操作進(jìn)行重排序瑟蜈。?因此它保障了?volatile?讀操作之后的任何操作開始執(zhí)行之前烟逊,寫線程對相關(guān)共享變量(包括?volatile?變量和普通變量)的更新已經(jīng)對當(dāng)前線程可見。

另外铺根,volatile關(guān)鍵字也可以被看作給?JIT?編譯器的一個(gè)提示宪躯,它相當(dāng)于告訴?JIT?編譯器相應(yīng)變量的值可能被其他處理器更改,從而使JIT編譯器不會對相應(yīng)代碼做出一些優(yōu)化而導(dǎo)致可見性問題夷都。volatile?在有序性保障方面也可以從禁止重排序的角度理解眷唉,即volatile?禁止了如下重排序?:寫?volatile?變量操作與該操作之前的任何讀?、寫操作不會被重排序囤官;?讀?volatile?變量操作與該操作之后的任何讀冬阳、寫操作不會被重排序。

綜上所述党饮,我們知道volatile關(guān)鍵字的作用體現(xiàn)在對其所修飾的變量的讀?肝陪、寫操作上。

如果被修飾的變量是個(gè)數(shù)組刑顺,那么volatile關(guān)鍵字只能夠?qū)?shù)組引用本身的操作(?讀取數(shù)組引用和更新數(shù)組引用?)起作用氯窍,而無法對數(shù)組元素的操作(?讀取?、更新數(shù)組元素?)起作用蹲堂。

對數(shù)組的操作可分為讀取數(shù)組元素狼讨、寫數(shù)組元素和讀取數(shù)組引用這幾種類型:

在上述操作中,類型①操作可以分解為兩個(gè)子步驟:先讀取數(shù)組引用?anArray?柒竞,?接著讀取數(shù)組中的第?0?個(gè)元素政供。這里,第1個(gè)子步驟實(shí)際上是讀取一個(gè)引用(?相當(dāng)于相應(yīng)數(shù)組的內(nèi)存地址,或者干脆理解為?C?語言中的指針)布隔,該子步驟是個(gè)?volatile??變量讀取操作离陶,它保障了當(dāng)前線程能夠讀取到數(shù)組引用本身的相對新值;而第?2?個(gè)子步驟則是在指定的數(shù)組引用?(?內(nèi)存地址?)?基礎(chǔ)上計(jì)算偏移量來讀取數(shù)組元素衅檀,它與?volatile關(guān)鍵字沒有關(guān)系招刨。?因此,它不能保障其讀取到的值是相對新值哀军。也就是說沉眶,在類型①操作中,volatile關(guān)鍵字起到的作用是保障當(dāng)前線程能夠讀取到的數(shù)組引用的相對新值排苍,這個(gè)值僅僅代表相應(yīng)數(shù)組的內(nèi)存地址而己沦寂,而該操作所讀取到的數(shù)組元素值是否是相對新值則無法通過?volatile?關(guān)鍵字保障学密。類似地淘衙,在類型②操作中,volatile關(guān)鍵字起到的作用只是保障讀取到的數(shù)組引用是一個(gè)相對新值腻暮,而對相應(yīng)數(shù)組元素的寫操作則沒有可見性保障彤守。類型③的操作是將一個(gè)數(shù)組的引用寫入另外一個(gè)數(shù)組?,這相當(dāng)于更新另外一個(gè)數(shù)組的引用?(?內(nèi)存地址?)哭靖,?這里的賦值操作是能夠觸發(fā)volatile關(guān)鍵字的所有作用的具垫。

如果要使對數(shù)組元素的讀、寫操作也能夠觸發(fā)volatile關(guān)鍵字的作用试幽,那么我們可以使用類?AtomiclntegerArray?筝蚕、AtomicLongArray?和?AtomicReferenceArray。

類似地铺坞,對于引用型volatile變量起宽,volatile?關(guān)鍵字只是保證讀線程能夠讀取到一個(gè)指向?qū)ο蟮南鄬π碌膬?nèi)存地址(引用),而這個(gè)內(nèi)存地址指向的對象的實(shí)例/靜態(tài)變量值是否是相對新的則沒有保障济榨。

volatile變量的開銷

volatile變量的開銷包括讀變量和寫變量兩個(gè)方面坯沪。volatile?變量的讀?、寫操作都不會導(dǎo)致上下文切換擒滑。因此?volatile?的開銷比鎖要小腐晾。寫一個(gè)?volatile?變量會使該操作以及該操作之前的任何寫操作的結(jié)果對其他處理器是可同步的,因此?volatile?變量寫操作的成本介于普通變量的寫操作和在臨界區(qū)內(nèi)進(jìn)行的寫操作之間丐一。讀取?volatile?變量的成本也比在臨界區(qū)中讀取變量要低(沒有鎖的申請與釋放以及上下文切快的開銷)藻糖。但是其成本可能比讀取普通變量要高一些。這是因?yàn)?volatile?變量的值每次都需要從高速緩存或者主內(nèi)存中讀取库车,而無法被暫存在寄存器中巨柒,從而元法發(fā)揮訪問的高效性。

volatile的典型應(yīng)用場景與實(shí)戰(zhàn)案例

volatile除了用于保障?long/double?型變量的讀、寫操作的原子性潘拱,其典型使用場景還包括以下幾個(gè)方面疹鳄。

·場景一?使用??volatile?變量作為狀態(tài)標(biāo)志。在該場景中芦岂,應(yīng)用程序的某個(gè)狀態(tài)由一個(gè)線程設(shè)置瘪弓,其他線程會讀取該狀態(tài)并以該狀態(tài)作為其計(jì)算的依據(jù)(?或者僅僅讀取并輸出這個(gè)狀態(tài)值。此時(shí)使用?volatile變量作為同步機(jī)制的好處是一個(gè)線程能夠?“通知”?另外一個(gè)線程某種事件(?例如禽最,網(wǎng)絡(luò)連接斷連之后重新連上)的發(fā)生腺怯,而這些線程又無須因此而使用鎖,從而避免了鎖的開銷以及相關(guān)問題川无。

·場景二??使用?volatile??保障可見性呛占。在該場景中,多個(gè)線程共享一個(gè)可變狀態(tài)變量?懦趋,其中一個(gè)線程更新了該變量之后晾虑。其他線程在元須加鎖的情況下也能夠看到該更新。

·場景三?使用?volatile變量替代鎖仅叫。volatile?關(guān)鍵字并非鎖的替代品帜篇,但是在一定的條件下它比鎖更合適?(?性能開銷小?、代碼簡單?)诫咱。多個(gè)線程共享一組可變狀態(tài)變量的時(shí)候笙隙,通常我們需要使用鎖來保障對這些變量的更新操作的原子性,以避免產(chǎn)生數(shù)據(jù)不一致問題坎缭。利用?volatile?變量寫操作具有的原子性?竟痰,我們可以把這一組可變狀態(tài)變量封裝成一個(gè)對象,那么對這些狀態(tài)變量的更新操作就可以通過創(chuàng)建一個(gè)新的對象并將該對象引用賦值給相應(yīng)的引用型變量來實(shí)現(xiàn)掏呼。在這個(gè)過程中坏快,?volatile?保障了原子性和可見性。從而避免了鎖的使用哄尔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末假消,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子岭接,更是在濱河造成了極大的恐慌富拗,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸣戴,死亡現(xiàn)場離奇詭異啃沪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)窄锅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門创千,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缰雇,“玉大人,你說我怎么就攤上這事追驴⌒涤矗” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵殿雪,是天一觀的道長暇咆。 經(jīng)常有香客問我,道長丙曙,這世上最難降的妖魔是什么爸业? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮亏镰,結(jié)果婚禮上扯旷,老公的妹妹穿的比我還像新娘。我一直安慰自己索抓,他們只是感情好钧忽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纸兔,像睡著了一般惰瓜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上汉矿,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音备禀,去河邊找鬼洲拇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛曲尸,可吹牛的內(nèi)容都是我干的赋续。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼另患,長吁一口氣:“原來是場噩夢啊……” “哼纽乱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昆箕,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鸦列,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鹏倘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薯嗤,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年纤泵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骆姐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖玻褪,靈堂內(nèi)的尸體忽然破棺而出肉渴,到底是詐尸還是另有隱情,我是刑警寧澤带射,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布黄虱,位于F島的核電站,受9級特大地震影響庸诱,放射性物質(zhì)發(fā)生泄漏捻浦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一桥爽、第九天 我趴在偏房一處隱蔽的房頂上張望朱灿。 院中可真熱鬧,春花似錦钠四、人聲如沸盗扒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侣灶。三九已至,卻和暖如春缕碎,著一層夾襖步出監(jiān)牢的瞬間褥影,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工咏雌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凡怎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓赊抖,卻偏偏與公主長得像统倒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子氛雪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

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