說一下對volatile的理解

volatile關(guān)鍵字是Java虛擬機(jī)提供的的最輕量級的同步機(jī)制瞻惋,它作為一個(gè)修飾符括袒,用來修飾變量次兆。它保證變量對所有線程可見性,禁止指令重排锹锰,但是不保證原子性芥炭。

Java 內(nèi)存模型/JMM(Java Memory Model)

Java 內(nèi)存模型(JSR-133)屏蔽了硬件、操作系統(tǒng)的差異恃慧,實(shí)現(xiàn)讓Java程序在各種平臺下都能達(dá)到一致的并發(fā)效果园蝠,規(guī)定了一個(gè)線程如何和何時(shí)可以看到由其他線程修改過后的共享變量的值,以及在必須時(shí)如何同步的訪問共享變量痢士,JMM使用內(nèi)存屏障提供了java程序運(yùn)行時(shí)統(tǒng)一的內(nèi)存模型彪薛。

JMM 最重要的三點(diǎn)內(nèi)容:重排序、原子性、內(nèi)存可見性陪汽。

原子性

由Java內(nèi)存模型來直接保證的原子性變量操作包括read训唱、load、use挚冤、assign、store赞庶、write這六個(gè)训挡, 我們大致可以認(rèn)為,基本數(shù)據(jù)類型的訪問歧强、讀取和賦值都是具備原子性的澜薄。

內(nèi)存可見性

可見性就是指當(dāng)一個(gè)線程修改了共享變量的值時(shí),其他線程能夠立即得知這個(gè)修改摊册。Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存肤京,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實(shí)現(xiàn)可見性的。

重排序

指令重排序是JVM為了優(yōu)化指令茅特,提高程序運(yùn)行效率忘分,在不影響 單線程程序 執(zhí)行結(jié)果的前提下,盡可能地提高并行度白修。編譯器妒峦、處理器也遵循這樣一個(gè)目標(biāo)。注意是單線程兵睛。多線程的情況下指令重排序就會(huì)給程序帶來問題肯骇。編譯器和處理器都有可能優(yōu)化執(zhí)行順序。

volatile關(guān)鍵字是怎么解決內(nèi)存可見性和重排序的祖很?

操作系統(tǒng)層面

CPU層提供了兩種解決方案:總線鎖和緩存一致性(緩存鎖)笛丙。

1、總線鎖

前端總線(也叫CPU總線)是所有CPU與芯片組連接的主干道假颇,負(fù)責(zé)CPU與外界所有部件的通信胚鸯,包括高速緩存、內(nèi)存拆融、北橋蠢琳,其控制總線向各個(gè)部件發(fā)送控制信號、通過地址總線發(fā)送地址信號指定其要訪問的部件镜豹、通過數(shù)據(jù)總線雙向傳輸傲须。

比如CPU1要操作共享內(nèi)存數(shù)據(jù)時(shí),先在總線上發(fā)出一個(gè)LOCK#信號趟脂,其他處理器就不能操作緩存了該共享變量內(nèi)存地址的緩存泰讽,也就是阻塞了其他CPU,使該處理器可以獨(dú)享此共享內(nèi)存。

很顯然已卸,這樣的做法代價(jià)十分昂貴佛玄,于是為了降低鎖粒度,CPU引入了緩存鎖累澡。

2梦抢、緩存一致性(緩存鎖)

緩存一致性:緩存一致性機(jī)制整體來說,就是當(dāng)某塊CPU對緩存中的數(shù)據(jù)進(jìn)行操作了之后愧哟,會(huì)通知其他CPU放棄儲存在它們內(nèi)部的緩存奥吩,或者從主內(nèi)存中重新讀取。

緩存鎖的核心機(jī)制就是基于緩存一致性協(xié)議來實(shí)現(xiàn)的蕊梧,即一個(gè)處理器的緩存回寫到內(nèi)存會(huì)導(dǎo)致其他處理器的緩存無效霞赫,IA-32處理器和Intel 64處理器使用MESI實(shí)現(xiàn)緩存一致性協(xié)議。

緩存一致性是一個(gè)協(xié)議肥矢,不同處理器的具體實(shí)現(xiàn)會(huì)有所不同端衰,MESI是一種比較常見的緩存一致性協(xié)議實(shí)現(xiàn)。

緩存一致性協(xié)議的實(shí)現(xiàn)方式

寫緩存(Store Buffer)

基于存儲緩存甘改,CPU將要寫入內(nèi)存數(shù)據(jù)先寫入Store Bufferes中旅东,同時(shí)發(fā)送消息,然后就可以繼續(xù)處理其他指令了楼誓。當(dāng)收到所有其他CPU的失效確認(rèn)(Invalidate Acknowledge)時(shí)玉锌,數(shù)據(jù)才會(huì)最終被提交∨备可以理解為異步寫入不需要等待其他CPU響應(yīng)結(jié)果主守。

舉例說明一下Store Bufferes的執(zhí)行流程:比如將內(nèi)存中共享變量a的值由1修改為66。

第一步榄融,CPU-0把a(bǔ)=66寫入Store Bufferes中参淫,然后發(fā)送Invalid消息給其他CPU,無需等待其他CPU相應(yīng)愧杯,便可繼續(xù)執(zhí)行其他指令了涎才。

第二步,當(dāng)CPU-0收到其他所有CPU對Invalid通知的相應(yīng)之后力九,再把Store Bufferes中的共享變量同步到緩存和主內(nèi)存中耍铜。

Store Forward(存儲轉(zhuǎn)發(fā))

Store Bufferes的引入提升了CPU的利用效率,但又帶來了新的問題跌前。在上述第一步中棕兼,Store Bufferes中的數(shù)據(jù)還未同步到CPU-0自己的緩存中,如果此時(shí)CPU-0需要讀取該變量a抵乓,緩存中的數(shù)據(jù)并不是最新的伴挚,所以CPU需要先讀取Store Bufferes中是否有值靶衍。如果有則直接讀取,如果沒有再到自己緩存中讀取茎芋,這就是所謂的”Store Forward“颅眶。

無效化隊(duì)列(Invalidate Queue)

CPU將數(shù)據(jù)寫入Store Bufferes的同時(shí)還會(huì)發(fā)消息給其他CPU,由于Store Bufferes空間較小田弥,且其他CPU可能正在處理其他事情涛酗,沒辦法及時(shí)回復(fù),這個(gè)消息就會(huì)陷入等待偷厦。

為了避免接收消息的CPU無法及時(shí)處理Invalid失效數(shù)據(jù)的消息煤杀,造成CPU指令等待,就在接收CPU中添加了一個(gè)異步消息隊(duì)列沪哺。消息發(fā)送方將數(shù)據(jù)失效消息發(fā)送到這個(gè)隊(duì)列中,接收CPU返回已接收酌儒,發(fā)送方CPU就可以繼續(xù)執(zhí)行后續(xù)操作了辜妓。而接收方CPU再慢慢處理”失效隊(duì)列“中的消息。

由于緩存一致性協(xié)議可能還會(huì)存在亂序問題忌怎,所有就需要通過內(nèi)存屏障來解決亂序問題籍滴。

使用內(nèi)存屏障后,寫入數(shù)據(jù)時(shí)會(huì)保證所有指令都執(zhí)行完畢榴啸,這樣就能保證修改過的數(shù)據(jù)能夠即時(shí)暴露給其他CPU孽惰。而讀取數(shù)據(jù)時(shí),能夠保證所有“失效隊(duì)列”消息都消費(fèi)完畢鸥印。然后勋功,CPU根據(jù)Invalid消息判斷自己緩存狀態(tài),正確讀寫數(shù)據(jù)库说。

3狂鞋、內(nèi)存屏障

CPU層面提供了三類內(nèi)存屏障:

寫屏障(Store Memory Barrier):告訴處理器在寫屏障之前將所有存儲在存儲緩存(store bufferes)中的數(shù)據(jù)同步到主內(nèi)存。也就是說當(dāng)看到Store Barrier指令潜的,就必須把該指令之前所有寫入指令執(zhí)行完畢才能繼續(xù)往下執(zhí)行骚揍。

讀屏障(Load Memory Barrier):處理器在讀屏障之后的讀操作,都在讀屏障之后執(zhí)行啰挪。也就是說在Load屏障指令之后就能夠保證后面的讀取數(shù)據(jù)指令一定能夠讀取到最新的數(shù)據(jù)信不。

全屏障(Full Memory Barrier):兼具寫屏障和讀屏障的功能。確保屏障前的內(nèi)存讀寫操作的結(jié)果提交到內(nèi)存之后亡呵,再執(zhí)行屏障后的讀寫操作抽活。

總之,內(nèi)存屏障的作用可以通過防止CPU亂序執(zhí)行來保證共享數(shù)據(jù)在多線程下的可見性政己。

Jave層面:

JVM規(guī)范的happens-before規(guī)則酌壕,一共八種掏愁,volatile變量規(guī)則:對一個(gè)volatile域的寫,happens-before于任意后續(xù)對這個(gè)volatile域的讀卵牍。說白了就是JVM規(guī)定如果一個(gè)操作happens-before另一個(gè)操作果港,那么第一個(gè)操作的執(zhí)行結(jié)果將對第二個(gè)操作可見,而且第一個(gè)操作的執(zhí)行順序排在第二個(gè)操作之前糊昙。

HotSpot實(shí)現(xiàn)是用“內(nèi)存屏障” 的方式來防止指令被重排序辛掠,為了實(shí)現(xiàn)volatile的內(nèi)存語義,編譯器在生成字節(jié)碼時(shí)释牺,會(huì)在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序萝衩。大多數(shù)的處理器都支持內(nèi)存屏障的指令。

  • LoadLoad Barriers:示例没咙,Load1猩谊;LoadLoad;Load2祭刚,確保Load1數(shù)據(jù)的裝載先于Load2及所有后續(xù)指令的裝載牌捷;
  • StoreStore Barriers:示例,Store1涡驮;StoreStore暗甥;Store2,確保Store1數(shù)據(jù)對其他處理器可見(刷新到內(nèi)存)先于Store2及所有后續(xù)存儲指令的存儲捉捅;
  • LoadStore Barriers:示例撤防,Load1;LoadStore棒口;Store2寄月,確保Load1數(shù)據(jù)裝載先于Store2及所有后續(xù)存儲指令刷新到內(nèi)存;
  • StoreLoad Barriers:示例陌凳,Store1剥懒;StoreLoad;Load2合敦,確保Store1數(shù)據(jù)對其他處理器變得可見(刷新到內(nèi)存)先于Load2及所有后續(xù)裝載指令的裝載初橘。StoreLoad Barriers會(huì)使該屏障之前的所有內(nèi)存訪問指令(存儲和裝載指令)完成之后,才執(zhí)行該屏障之后的內(nèi)存訪問指令充岛。

JMM內(nèi)存屏障插入策略:

  • 在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障保檐。
  • 在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障。
  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障崔梗。
  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障夜只。

volatile的使用場景?

項(xiàng)目當(dāng)中的使用場景

1.狀態(tài)標(biāo)志蒜魄,比如我們工程中經(jīng)常用一個(gè)變量標(biāo)識程序是否啟動(dòng)扔亥、初始化完成场躯、是否停止等

2.懶漢式單例模式,我們常用的 double-check 的單例模式

各個(gè)框架當(dāng)中的使用場景

1.AQS中的state字段是用volatile修飾的旅挤,配合CAS操作做到無鎖安全修改共享變量踢关。

2.J.U.C包中的各種原子操作類,里面的操作的數(shù)據(jù)也是用volatile修飾的粘茄,配合CAS操作做到無鎖安全修改共享變量签舞。

3.線程池中的ctl字段,也就是線程狀態(tài)+線程數(shù)量字段柒瓣。

基本上有CAS操作的屬性都需要用volatile修飾儒搭。volatile(可見性、有序性)+CAS(原子性)

參考文章:

講真芙贫,你是沒懂 volatile 的設(shè)計(jì)原理搂鲫,所以不會(huì)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市磺平,隨后出現(xiàn)的幾起案子默穴,更是在濱河造成了極大的恐慌,老刑警劉巖褪秀,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異薛训,居然都是意外死亡媒吗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門乙埃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闸英,“玉大人,你說我怎么就攤上這事介袜「危” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵遇伞,是天一觀的道長辙喂。 經(jīng)常有香客問我,道長鸠珠,這世上最難降的妖魔是什么巍耗? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮渐排,結(jié)果婚禮上炬太,老公的妹妹穿的比我還像新娘。我一直安慰自己驯耻,他們只是感情好亲族,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布炒考。 她就那樣靜靜地躺著,像睡著了一般霎迫。 火紅的嫁衣襯著肌膚如雪斋枢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天女气,我揣著相機(jī)與錄音杏慰,去河邊找鬼。 笑死炼鞠,一個(gè)胖子當(dāng)著我的面吹牛缘滥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谒主,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼朝扼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了霎肯?” 一聲冷哼從身側(cè)響起擎颖,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎观游,沒想到半個(gè)月后搂捧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懂缕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年允跑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搪柑。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡聋丝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出工碾,到底是詐尸還是另有隱情弱睦,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布渊额,位于F島的核電站况木,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏旬迹。R本人自食惡果不足惜焦读,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舱权。 院中可真熱鬧矗晃,春花似錦、人聲如沸宴倍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俗他,卻和暖如春脖捻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兆衅。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工地沮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羡亩。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓摩疑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親畏铆。 傳聞我的和親對象是個(gè)殘疾皇子雷袋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

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