緩存一致性問(wèn)題
? ? ? ? 內(nèi)存模型描述的是程序中各變量(實(shí)例域谎替、靜態(tài)域和數(shù)組元素)之間的關(guān)系,以及計(jì)算機(jī)系統(tǒng)將變量存儲(chǔ)到內(nèi)存和從內(nèi)存取出變量這樣的底層細(xì)節(jié)蹋辅。計(jì)算機(jī)在運(yùn)行程序時(shí)钱贯,每條指令都是在cpu中執(zhí)行的,執(zhí)行指令時(shí)會(huì)涉及到數(shù)據(jù)的讀寫(xiě)而程序運(yùn)行的數(shù)據(jù)是存儲(chǔ)在主內(nèi)存中的侦另,顯然在主內(nèi)存讀取數(shù)據(jù)沒(méi)有cpu執(zhí)行指令的速度快喷舀,如果所有的交互都需要和主存打交道則會(huì)大大影響效率,所以就有了高速緩存淋肾。有了高速緩存會(huì)帶來(lái)數(shù)據(jù)一致性問(wèn)題硫麻,不同核心的緩存和主存值在某一瞬間值不一致。具體在java內(nèi)存模型中樊卓,有main memory拿愧,每個(gè)線程也有自己的工作區(qū),java內(nèi)存模型規(guī)定碌尔,一個(gè)線程要在自己的工作區(qū)中保存要訪問(wèn)的變量的副本浇辜,之后線程直接修改副本變量的值,在修改完之后唾戚,把線程變量副本的值寫(xiě)回到對(duì)象在堆中變量柳洋,而不能直接修改主內(nèi)存的變量,這就可能導(dǎo)致同一個(gè)變量在某個(gè)瞬間叹坦,一個(gè)線程的memory中的值可能與另一個(gè)線程memory或者main memory中的值不一致?熊镣。為了解決緩存一致性,CPU制造商制定了一個(gè)規(guī)則:當(dāng)一個(gè)CPU修改緩存中的字節(jié)時(shí),其他CPU會(huì)被通知募书,它們的緩存將視為無(wú)效绪囱。
volatile到底做了什么?
? ? ? -server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*LazySingleton.getInstance
查看生成的匯編指令:
對(duì)于volatile修飾的變量在生成的匯編指令前有#lock前綴莹捡,#lock前綴是用來(lái)干什么的呢鬼吵?
1. 鎖總線,其它CPU對(duì)內(nèi)存的讀寫(xiě)請(qǐng)求都會(huì)被阻塞篮赢,直到鎖釋放齿椅,不過(guò)實(shí)際后來(lái)的處理器都采用鎖緩存替代鎖總線琉挖,因?yàn)殒i總線的開(kāi)銷(xiāo)比較大,鎖總線期間其他CPU沒(méi)法訪問(wèn)內(nèi)存
2 不是內(nèi)存屏障卻能完成類(lèi)似內(nèi)存屏障的功能涣脚,阻止指令重排序
?鎖總線中由于效率問(wèn)題示辈,后來(lái)的處理器都通過(guò)緩存一致性協(xié)議來(lái)保證多緩存的數(shù)據(jù)一致性。?這類(lèi)協(xié)議就是要使多組緩存的內(nèi)容保持一致涩澡。
緩存一致性協(xié)議
CPU緩存不僅僅在做內(nèi)存?zhèn)鬏數(shù)臅r(shí)候才與總線打交道顽耳,而是不停在嗅探總線上發(fā)生的數(shù)據(jù)交換,跟蹤其他緩存在做什么妙同。所以當(dāng)一個(gè)緩存代表它所屬的處理器去讀寫(xiě)內(nèi)存時(shí)射富,其它處理器都會(huì)得到通知,它們以此來(lái)使自己的緩存保持同步粥帚。只要某個(gè)處理器一寫(xiě)內(nèi)存胰耗,其它處理器馬上知道這塊內(nèi)存在它們的緩存段中已失效。
處理器上有一套完整的協(xié)議芒涡,來(lái)保證Cache一致性柴灯。比較經(jīng)典的Cache一致性協(xié)議當(dāng)屬M(fèi)ESI協(xié)議,奔騰處理器有使用它费尽,很多其他的處理器都是使用它的變種赠群。
單核Cache中每個(gè)Cache line有2個(gè)標(biāo)志:dirty和valid標(biāo)志,它們很好的描述了Cache和Memory(內(nèi)存)之間的數(shù)據(jù)關(guān)系(數(shù)據(jù)是否有效旱幼,數(shù)據(jù)是否被修改)查描,而在多核處理器中,多個(gè)核會(huì)共享一些數(shù)據(jù)柏卤,MESI協(xié)議就包含了描述共享的狀態(tài)冬三。
???????? 在MESI協(xié)議中,每個(gè)Cache line有4個(gè)狀態(tài)缘缚,可用2個(gè)bit表示勾笆,它們分別是:
?M(Modified)和E(Exclusive)狀態(tài)的Cache line,數(shù)據(jù)是獨(dú)有的桥滨,不同點(diǎn)在于M狀態(tài)的數(shù)據(jù)是dirty的(和內(nèi)存的不一致)窝爪,E狀態(tài)的數(shù)據(jù)是clean的(和內(nèi)存的一致)。
?????? 只有Core 0訪問(wèn)變量x该园,它的Cache line狀態(tài)為E(Exclusive)酸舍。
???????? 3個(gè)Core都訪問(wèn)變量x,它們對(duì)應(yīng)的Cache line為S(Shared)狀態(tài)里初。
?? Core 0修改了x的值之后,這個(gè)Cache line變成了M(Modified)狀態(tài)忽舟,其他Core對(duì)應(yīng)的Cache line變成了I(Invalid)狀態(tài)双妨。
在MESI協(xié)議中淮阐,每個(gè)Cache的Cache控制器不僅知道自己的讀寫(xiě)操作,而且也監(jiān)聽(tīng)(snoop)其它Cache的讀寫(xiě)操作刁品。每個(gè)Cache line所處的狀態(tài)根據(jù)本核和其它核的讀寫(xiě)操作在4個(gè)狀態(tài)間進(jìn)行遷移泣特。
一個(gè)緩存除在Invalid狀態(tài)外都可以滿足cpu的讀請(qǐng)求,一個(gè)invalid的緩存行必須從主存中讀忍羲妗(變成S或者 E狀態(tài))來(lái)滿足該CPU的讀請(qǐng)求状您。
一個(gè)寫(xiě)請(qǐng)求只有在該緩存行是M或者E狀態(tài)時(shí)才能被執(zhí)行,如果緩存行處于S狀態(tài)兜挨,必須先將其它緩存中該緩存行變成Invalid狀態(tài)(也即是不允許不同CPU同時(shí)修改同一緩存行膏孟,即使修改該緩存行中不同位置的數(shù)據(jù)也不允許)。該操作經(jīng)常作用廣播的方式來(lái)完成拌汇,例如:Request For Ownership (RFO)
緩存可以隨時(shí)將一個(gè)非M狀態(tài)的緩存行作廢柒桑,或者變成Invalid狀態(tài),而一個(gè)M狀態(tài)的緩存行必須先被寫(xiě)回主存噪舀。
一個(gè)處于M狀態(tài)的緩存行必須時(shí)刻監(jiān)聽(tīng)所有試圖讀該緩存行相對(duì)就主存的操作魁淳,這種操作必須在緩存將該緩存行寫(xiě)回主存并將狀態(tài)變成S狀態(tài)之前被延遲執(zhí)行。
一個(gè)處于S狀態(tài)的緩存行也必須監(jiān)聽(tīng)其它緩存使該緩存行無(wú)效或者獨(dú)享該緩存行的請(qǐng)求与倡,并將該緩存行變成無(wú)效(Invalid)界逛。
一個(gè)處于E狀態(tài)的緩存行也必須監(jiān)聽(tīng)其它緩存讀主存中該緩存行的操作,一旦有這種操作纺座,該緩存行需要變成S狀態(tài)息拜。
對(duì)于M和E狀態(tài)而言總是精確的,他們?cè)诤驮摼彺嫘械恼嬲隣顟B(tài)是一致的比驻。而S狀態(tài)可能是非一致的该溯,如果一個(gè)緩存將處于S狀態(tài)的緩存行作廢了,而另一個(gè)緩存實(shí)際上可能已經(jīng)獨(dú)享了該緩存行别惦,但是該緩存卻不會(huì)將該緩存行升遷為E狀態(tài)狈茉,這是因?yàn)槠渌彺娌粫?huì)廣播他們作廢掉該緩存行的通知,同樣由于緩存并沒(méi)有保存該緩存行的copy的數(shù)量掸掸,因此(即使有這種通知)也沒(méi)有辦法確定自己是否已經(jīng)獨(dú)享了該緩存行氯庆。
從上面的意義看來(lái)E狀態(tài)是一種投機(jī)性的優(yōu)化:如果一個(gè)CPU想修改一個(gè)處于S狀態(tài)的緩存行,總線事務(wù)需要將所有該緩存行的copy變成invalid狀態(tài)扰付,而修改E狀態(tài)的緩存不需要使用總線事務(wù)堤撵。
只有當(dāng)緩存行處于E或者M(jìn)狀態(tài)時(shí),處理器才能去寫(xiě)它羽莺,也就是說(shuō)只有在這兩種狀態(tài)下实昨,處理器是獨(dú)占這個(gè)緩存行的。當(dāng)處理器想寫(xiě)某個(gè)緩存行時(shí)盐固,如果它沒(méi)有獨(dú)占權(quán)荒给,它必須先發(fā)送一條"我要獨(dú)占權(quán)"的請(qǐng)求給總線丈挟,這會(huì)通知其它處理器把它們擁有的同一緩存段的拷貝失效(如果有)。只有在獲得獨(dú)占權(quán)后志电,處理器才能開(kāi)始修改數(shù)據(jù)----并且此時(shí)這個(gè)處理器知道曙咽,這個(gè)緩存行只有一份拷貝,在我自己的緩存里挑辆,所以不會(huì)有任何沖突(其他核的緩存行都被置為無(wú)效了)例朱。反之,如果有其它處理器想讀取這個(gè)緩存行(馬上能知道鱼蝉,因?yàn)橐恢痹谛崽娇偩€)洒嗤,獨(dú)占或已修改的緩存行必須先回到"共享"狀態(tài)。如果是已修改的緩存行蚀乔,那么還要先把內(nèi)容回寫(xiě)到內(nèi)存中烁竭。
以上是從硬件層面來(lái)解釋,java提供的volatile關(guān)鍵字是對(duì)這些硬件底層細(xì)節(jié)的封裝吉挣,下面從java內(nèi)存模型角度來(lái)看看jmm希望volatile達(dá)到的效果派撕。
? ? ? ? ? ?java內(nèi)存模型把一個(gè)線程可以執(zhí)行的動(dòng)作有使用(use),賦值assign睬魂,裝載load终吼、存儲(chǔ)store、鎖定lock氯哮、解鎖unlock际跪,而主內(nèi)存執(zhí)行的動(dòng)作有read,write喉钢、lock姆打、unlock,每一個(gè)這樣的動(dòng)作都是原子的肠虽。使用和賦值操作是線程的執(zhí)行引擎和線程的工作存儲(chǔ)之間緊密耦合(一步就可以完成的)的交互過(guò)程幔戏,但主內(nèi)存和線程的工作存儲(chǔ)間的數(shù)據(jù)傳送是松散耦合的,當(dāng)數(shù)據(jù)從主內(nèi)存復(fù)制到工作存儲(chǔ)税课,必須出現(xiàn)兩個(gè)動(dòng)作:由主內(nèi)存執(zhí)行的read操作闲延,一段時(shí)間后由線程執(zhí)行的相應(yīng)load動(dòng)作到線程的工作存儲(chǔ)。反之韩玩,由線程執(zhí)行store操作垒玲,一段時(shí)間后由主內(nèi)存執(zhí)行相應(yīng)的write動(dòng)作完成線程的工作存儲(chǔ)到主內(nèi)存的數(shù)據(jù)復(fù)制。在主內(nèi)存和線程的工作存儲(chǔ)間傳送數(shù)據(jù)需要一定的傳送時(shí)間找颓,并且每次傳送的時(shí)間可能是不同的合愈,因此,在另一個(gè)線程看來(lái),線程對(duì)不同變量所執(zhí)行的動(dòng)作可能是按照不同的順序(相對(duì)程序代碼語(yǔ)義順序)執(zhí)行的想暗,(比如線程內(nèi)的程序代碼是先給變量a賦值妇汗,再給b賦值帘不,而在另一個(gè)線程卻可能先看到主內(nèi)存中b變量的更新说莫,再看到a變量的更新)。
每種操作的詳細(xì)定義
? ? ? ? ? ?線程的use動(dòng)作把一個(gè)變量的線程工作拷貝的內(nèi)容傳送給線程執(zhí)行引擎寞焙。每當(dāng)線程執(zhí)行一個(gè)用到變量的值的虛擬機(jī)指令時(shí)執(zhí)行這個(gè)動(dòng)作储狭。
? ? ? ? ? ? 線程的assign動(dòng)作把一個(gè)值從線程執(zhí)行引擎?zhèn)魉偷阶兞康木€程工作拷貝。每當(dāng)線程執(zhí)行一個(gè)給變量賦值的虛擬機(jī)指令時(shí)執(zhí)行這個(gè)動(dòng)作捣郊。
? ? ? ? ? 主內(nèi)存的read動(dòng)作把一個(gè)變量的主內(nèi)存拷貝的內(nèi)容傳輸?shù)骄€程的工作內(nèi)存以便后面的load動(dòng)作使用辽狈。
? ? ? ? ? 線程的load動(dòng)作把read動(dòng)作從主內(nèi)存中得到的值放入變量的線程工作拷貝中。
? ? ? ? ? 線程的store動(dòng)作把一個(gè)變量的線程工作拷貝內(nèi)容傳送到主內(nèi)存中以便后面的write動(dòng)作使用呛牲。
? ? ? ? ? ?主內(nèi)存的write動(dòng)作把store動(dòng)作從線程工作內(nèi)存中得到的值放入主內(nèi)存中一個(gè)變量的主拷貝刮萌。
? ? ? ? ? ?和主內(nèi)存緊密同步的線程的lock動(dòng)作使線程獲得一個(gè)獨(dú)占鎖定的聲明。
? ? ? ? ? ?和主內(nèi)存緊密同步的線程的unlock動(dòng)作使線程釋放一個(gè)獨(dú)占鎖定的聲明娘扩。
? ? ? ? ? 這樣着茸,線程和變量的相互作用由use、assign琐旁、load和store動(dòng)作的序列組成涮阔。主內(nèi)存為每個(gè)load動(dòng)作執(zhí)行read動(dòng)作,為每個(gè)Store動(dòng)作執(zhí)行write動(dòng)作灰殴。線程的鎖定的相互作用由lock或unlock動(dòng)作順序組成敬特。
? ? ? ? ? ?變量規(guī)則: 不允許一個(gè)線程丟棄它最近的assign操作;不允許一個(gè)線程無(wú)原因地把數(shù)據(jù)從線程的工作存儲(chǔ)寫(xiě)回到主內(nèi)存牺陶,一個(gè)個(gè)新的變量只能在主內(nèi)存中產(chǎn)生并且不能在任何線程的工作內(nèi)存中初始化伟阔。假如動(dòng)作a是線程t對(duì)變量v執(zhí)行的load或store操作,動(dòng)作p是主內(nèi)存對(duì)變量v執(zhí)行的相應(yīng)read和write動(dòng)作掰伸,類(lèi)似地皱炉,假設(shè)動(dòng)作b是線程t對(duì)同一個(gè)變量v執(zhí)行的另外load或者store動(dòng)作,動(dòng)作q是主內(nèi)存對(duì)變量v執(zhí)行的相應(yīng)read或者write動(dòng)作碱工,如果a先于b娃承,必須有p先于q,(也即主內(nèi)存執(zhí)行對(duì)給定的一個(gè)變量的主拷貝動(dòng)作必須遵循線程執(zhí)行時(shí)要求的先后順序怕篷,注意历筝,這條規(guī)則只適用于一個(gè)線程對(duì)于同一個(gè)變量不同動(dòng)作的情況,是針對(duì)單線程提出的廊谓。對(duì)于volatile 類(lèi)型的變量有更嚴(yán)格的規(guī)則)
volatile變量
? ? ? ? ? volatile類(lèi)型變量規(guī)則:對(duì)于volatile變量梳猪,每個(gè)線程對(duì)該變量實(shí)施的動(dòng)作有以下附加的規(guī)則,假定t表示一個(gè)線程,v春弥,w表示volatile變量呛哟,
? ? ? ? ?1: 只有當(dāng)線程t對(duì)變量v執(zhí)行的前一個(gè)動(dòng)作是load的時(shí)候,線程t才能對(duì)變量v執(zhí)行use操作匿沛,并且扫责,只有當(dāng)線程t對(duì)變量v執(zhí)行的后一個(gè)動(dòng)作是use的時(shí)候,線程t才能對(duì)v執(zhí)行l(wèi)oad操作逃呼,這樣就保證了其它線程對(duì)變量 v修改后鳖孤,線程使用時(shí)必須先去主內(nèi)存加載。
? ? ? ? ?2:只有當(dāng)線程t對(duì)變量v執(zhí)行的前一個(gè)動(dòng)作是assign時(shí)候抡笼,線程t才能對(duì)變量v執(zhí)行store操作苏揣,并且只有當(dāng)線程t對(duì)變量v執(zhí)行的后一個(gè)動(dòng)作是store時(shí),線程t才能對(duì)v執(zhí)行assign動(dòng)作推姻,這樣保證修改后會(huì)立即寫(xiě)會(huì)主內(nèi)存
? ? ? ? ?3: 假定動(dòng)作a是線程t對(duì)變量v實(shí)施的use或assign動(dòng)作平匈,假定動(dòng)作f是和動(dòng)作a相關(guān)聯(lián)的load或store動(dòng)作,假定動(dòng)作p是和動(dòng)作f相對(duì)應(yīng)的對(duì)變量v的read或write動(dòng)作藏古,類(lèi)似的增炭,動(dòng)作b是線程t對(duì)變量w實(shí)施的use或assign動(dòng)作,動(dòng)作g是和動(dòng)作b相關(guān)聯(lián)的load或store動(dòng)作校翔,假定動(dòng)作q是和動(dòng)作g相應(yīng)的對(duì)變量w的read和write動(dòng)作弟跑。如果a先于b,那么p先于q防症。也即對(duì)變量v孟辑、w寫(xiě)會(huì)主內(nèi)存的順序與程序代碼對(duì)v、w賦值先后順序一致蔫敲。
? ? ? ?以上規(guī)則保證了饲嗽,對(duì)于申明為volatile的變量的每個(gè)use或assign動(dòng)作都要訪問(wèn)主內(nèi)存一次(這應(yīng)該是達(dá)到這種效果,并不會(huì)每次都會(huì)去訪問(wèn)主內(nèi)存奈嘿,多核硬件架構(gòu)下硬件去監(jiān)聽(tīng)其他核對(duì)共享變量的修改貌虾,然后對(duì)修改的變量,重新去讀主內(nèi)存)裙犹,并且按照線程的執(zhí)行語(yǔ)義所指定的次序訪問(wèn)主內(nèi)存尽狠。
volatile關(guān)鍵字規(guī)則:
? ? ? ?1: 在write volatile字段之后,其值會(huì)被flush出處理器cache叶圃,寫(xiě)回memory
? ? ? ?2: 在read volatile變量之前袄膏,會(huì)驗(yàn)證工作內(nèi)存是否有效
? ? ? ?3:?禁止reorder(重排序,即與原程序指定的順序不一致)任意兩個(gè)volatile變量,并且同時(shí)嚴(yán)格限制reorder volatile變量周?chē)姆莢olatile變量。這一點(diǎn)即volatile具有變量的“順序性”扫沼,即指令不會(huì)重新排序瓦胎,而是按照程序指定的順序執(zhí)行。
? ? ? 1: 編譯器重排序吃度。編譯器在不改變單線程程序語(yǔ)義的前提下项阴,可以重新安排語(yǔ)句的執(zhí)行順序喊式。
? ? ? 2: 處理器重排序锌奴。如果不存在數(shù)據(jù)依賴兽狭,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。
? ? ? ?重排序不會(huì)影響單線程的運(yùn)行結(jié)果缨叫,但對(duì)多線程會(huì)有影響椭符。在jvm底層volatile是采用內(nèi)存屏障來(lái)實(shí)現(xiàn)的有序性的。觀察volatile關(guān)鍵字所生成的匯編代碼發(fā)現(xiàn)耻姥,加入volatile后多處了一個(gè)lock前綴指令,lock前綴指令實(shí)際上就相當(dāng)于一個(gè)內(nèi)存屏障有咨,內(nèi)存屏障是一組處理指令琐簇,用來(lái)實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制。
? ? ? ? 一個(gè)變量申明為volatile座享,當(dāng)一個(gè)線程修改共享變量后婉商,它會(huì)立即被更新到主內(nèi)存中,系統(tǒng)會(huì)通知其他線程本地該變量的緩存無(wú)效(硬件自己實(shí)現(xiàn)的)渣叛,讀取時(shí)需要從主內(nèi)存重新讀取丈秩,如果一個(gè)使用動(dòng)作序列已經(jīng)讀取了就不會(huì)再重新讀取,重新讀取時(shí)針對(duì)一個(gè)新的使用序列淳衙。
例子講解
? ? ? ?上面結(jié)果輸出有可能是a=1蘑秽,b=4,可能發(fā)生了重排序箫攀,使得b=4在a=3前面執(zhí)行肠牲,如果是volatile變量,則會(huì)以程序語(yǔ)義執(zhí)行的順序?qū)懟刂鲀?nèi)存靴跛。
? ? ? ? ? ?如果pleasestop沒(méi)有被聲明為volatile缀雳,線程執(zhí)行run的時(shí)候檢查的是自己的副本,而不能及時(shí)得到其它線程已經(jīng)調(diào)用tellmetostop()修改了pleasestop的值梢睛。volatile關(guān)鍵字保證了變量在被修改時(shí)肥印,其它線程能夠立刻看到這個(gè)更新。
? ? ?在上面程序绝葡,如果一個(gè)線程已調(diào)用完構(gòu)造器深碱,new 先分配地址,但something的實(shí)例域還沒(méi)被賦值挤牛,這是對(duì)象還沒(méi)有被完全初始化好莹痢,如果存在重排序,會(huì)先賦值給instance引用,這樣另一個(gè)線程拿到instance所指向的對(duì)象其實(shí)是不完整的竞膳,在使用時(shí)會(huì)存在問(wèn)題航瞭。使用volatile修飾后,就不會(huì)存在重排序的情況坦辟,這樣就會(huì)確保將實(shí)例域的數(shù)據(jù)寫(xiě)回到主內(nèi)存的動(dòng)作在將實(shí)例賦值給instance引用動(dòng)作之前發(fā)生(即volatile的 happens-before 規(guī)則)刊侯,所以這樣就確保了在使用前對(duì)象已完全初始化完成。
? ? ? ?在新的內(nèi)存模型下锉走,線程可以在不使用同步的情況下看到該對(duì)象在構(gòu)造器里設(shè)置的final域滨彻。在構(gòu)造期間,不要公布this引用挪蹭!
? ? ? ? 能夠確保另一調(diào)用reader的線程它能看到f.x的值亭饵,因?yàn)閒.x是final的梁厉。但不能保證它看到的f.y是4辜羊,如果是引用類(lèi)型词顾,也會(huì)有這樣的保障八秃,在拿到final引用類(lèi)型前,這個(gè)引用所指向的對(duì)象的所有域是完全初始化完成肉盹。
彩蛋
大家有沒(méi)有想過(guò)既然有了mesi協(xié)議為什么還需要volatile關(guān)鍵字 昔驱?
一:
1. volatile和MESI差著好幾層抽象,中間會(huì)經(jīng)歷java編譯器上忍,java虛擬機(jī)和JIT骤肛,操作系統(tǒng),CPU核心睡雇。
volatile在Java中的意圖是保證變量的可見(jiàn)性萌衬。為了實(shí)現(xiàn)這個(gè)功能,必須保證1)編譯器不能亂序優(yōu)化它抱;2)指令執(zhí)行在CPU上要保證讀寫(xiě)的fence秕豫。
對(duì)于x86的體系結(jié)構(gòu),voltile變量的訪問(wèn)代碼會(huì)被java編譯器生成不亂序的观蓄,帶有l(wèi)ock指令前綴的機(jī)器碼混移。而lock的實(shí)現(xiàn)還要區(qū)分,這個(gè)數(shù)據(jù)在不在CPU核心的專有緩存中(一般是指L1/L2)侮穿。如果在歌径,MESI才有用武之地。如果不滿足就會(huì)要用其他手段亲茅。而這些手段是虛擬機(jī)開(kāi)發(fā)者回铛,以及操作系統(tǒng)開(kāi)發(fā)者需要考慮的問(wèn)題狗准。簡(jiǎn)而言之,CPU里的緩存茵肃,buffer腔长,queue有很多種。MESI只能在一種情況下解決核心專有Cache之間不一致的問(wèn)題验残。
此外捞附,如果有些CPU不支持MESI協(xié)議,那么必須用其他辦法來(lái)實(shí)現(xiàn)等價(jià)的效果您没,比如總是用鎖總線的方式鸟召,或者明確的fence指令來(lái)保證volatile想達(dá)到的目標(biāo)。
如果CPU是單核心的氨鹏,cache是專供這個(gè)核心的欧募,MESI理論上也就沒(méi)有用了。但是依然要考慮主存和Cache被多個(gè)線程切換訪問(wèn)時(shí)帶來(lái)的不一致問(wèn)題喻犁。
總之槽片,volatile是一個(gè)高層的表達(dá)意圖的“抽象”,而MESI是為了實(shí)現(xiàn)這個(gè)抽象肢础,在某種特定情況下需要使用的一個(gè)實(shí)現(xiàn)細(xì)節(jié)。
你可以把JSR-133看作是一套UT的規(guī)范碌廓。不管底下CPU/編譯器怎么折騰传轰,只要voltile修飾的變量滿足JSR-133所描述的所有場(chǎng)景,就算是一個(gè)好的java實(shí)現(xiàn)谷婆。而基于這個(gè)規(guī)范慨蛙,java開(kāi)發(fā)人員才能安心的開(kāi)發(fā)并發(fā)代碼,而不至于被底層細(xì)節(jié)搞瘋纪挎。
二:
首先期贫,volatile是java語(yǔ)言層面給出的保證,MSEI協(xié)議是多核cpu保證cache一致性的一種方法异袄,中間隔的還很遠(yuǎn)通砍,我們可以先來(lái)做幾個(gè)假設(shè):
回到遠(yuǎn)古時(shí)候,那個(gè)時(shí)候cpu只有單核烤蜕,或者是多核但是保證sequence consistency封孙,當(dāng)然也無(wú)所謂有沒(méi)有MESI協(xié)議了。那這個(gè)時(shí)候讽营,我們需要java語(yǔ)言層面的volatile的支持嗎虎忌?當(dāng)然是需要的,因?yàn)樵谡Z(yǔ)言層面編譯器和虛擬機(jī)為了做性能優(yōu)化橱鹏,可能會(huì)存在指令重排的可能膜蠢,而volatile給我們提供了一種能力堪藐,我們可以告訴編譯器,什么可以重排挑围,什么不可以礁竞。
那好,假設(shè)更進(jìn)一步贪惹,假設(shè)java語(yǔ)言層面不會(huì)對(duì)指令做任何的優(yōu)化重排苏章,那在多核cpu的場(chǎng)景下,我們還需要volatile關(guān)鍵字嗎奏瞬?答案仍然是需要的枫绅。因?yàn)?MESI只是保證了多核cpu的獨(dú)占cache之間的一致性,但是cpu的并不是直接把數(shù)據(jù)寫(xiě)入L1 cache的硼端,中間還可能有store buffer并淋。有些arm和power架構(gòu)的cpu還可能有l(wèi)oad buffer或者invalid queue等等。因此珍昨,有MESI協(xié)議遠(yuǎn)遠(yuǎn)不夠县耽。
3. 好的,到了現(xiàn)在這步镣典,我們?cè)賮?lái)做最后一個(gè)假設(shè)兔毙,假設(shè)cpu寫(xiě)cache都是按照指令順序fifo寫(xiě)的,那現(xiàn)在可以拋棄volatile了吧兄春?你覺(jué)得呢澎剥?我都寫(xiě)到標(biāo)題4了,那肯定不行案嫌摺哑姚!因?yàn)閷?duì)于arm和power這個(gè)weak consistency的架構(gòu)的cpu來(lái)說(shuō),它們只會(huì)保證指令之間有比如控制依賴芜茵,數(shù)據(jù)依賴叙量,地址依賴等等依賴關(guān)系的指令間提交的先后順序,而對(duì)于完全沒(méi)有依賴關(guān)系的指令九串,比如x=1;y=2绞佩,它們是不會(huì)保證執(zhí)行提交的順序的,除非你使用了volatile蒸辆,java把volatile編譯成arm和power能夠識(shí)別的barrier指令征炼,這個(gè)時(shí)候才是按順序的。
高級(jí)語(yǔ)言提供關(guān)鍵字躬贡,是為了屏蔽底層硬件和內(nèi)核的不一致性谆奥,x86平臺(tái)和其他cpu 對(duì)內(nèi)存一致性的保證有不同的程度,語(yǔ)言能封裝這種差異拂玻,方便了上層開(kāi)發(fā)者