中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

內(nèi)存屏障和 volatile 語義

2018-09-17    來源:importnew

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

背景

在閱讀java中volatile的關(guān)鍵詞語義時(shí),發(fā)現(xiàn)很多書中都使用了重排序這個(gè)詞來描述,同時(shí)又講到了線程工作內(nèi)存和主存等等相關(guān)知識。但是只用那些書的抽象定義進(jìn)行理解時(shí)總是感覺什么地方說不通,最后發(fā)現(xiàn),是那些書中使用的抽象屏蔽了一些對讀者的知識點(diǎn),反而導(dǎo)致了理解上的困難。因此有了這篇文章。沒有任何虛構(gòu)的理解抽象,從硬件的角度來理解什么是內(nèi)存屏障,以及內(nèi)存屏障如何讓volatile工作。最后說明了在多線程中,如何使用volatile來提升性能。

存儲結(jié)構(gòu)

在計(jì)算機(jī)之中,存在著多級的存儲結(jié)構(gòu)。這是為了適應(yīng)不同硬件速度帶來的差異。底層是主存(也就是內(nèi)存),容量最大,速度最慢;中間是cpu的緩存(現(xiàn)代cpu都有多級緩存結(jié)構(gòu),級別越高速度越慢,但是可以將多級緩存看成是一個(gè)整體),容量較小,但是速度很快;最上層是cpu自身的store buffer和Invalidate Queues,速度最快,容量非常少。其中主存和cpu緩存的數(shù)據(jù)視圖對于每一個(gè)cpu都是相同的,也就是說在這個(gè)級別上,每個(gè)cpu都看到了相同的數(shù)據(jù),而store buffer和Invalidate Queues是每一個(gè)cpu私有的。這就導(dǎo)致了一系列的編程問題,下文會詳細(xì)展開。

CPU緩存

Cpu為了平衡自身處理速度過快和主存讀寫速度過慢這個(gè)問題,使用了緩存來存儲處理中的熱點(diǎn)數(shù)據(jù)。cpu需要處理數(shù)據(jù)的時(shí)候(包含讀取和寫出),都是直接向緩存發(fā)出讀寫指令。如果數(shù)據(jù)不在緩存中,則會從主存中讀取數(shù)據(jù)到緩存中,再做對應(yīng)的處理。需要注意的是,cpu讀取數(shù)據(jù)到緩存中,是固定長度的讀取。也就是說cpu緩存是一行一行的載入數(shù)據(jù)進(jìn)來。因?yàn)橐卜Q之為cpu緩存行,即cacheline。而緩存中的數(shù)據(jù),也會在合適的時(shí)候回寫到主存當(dāng)中(這個(gè)時(shí)機(jī)可以抽象的認(rèn)為是由cpu自行決定的)。

現(xiàn)在的cpu都是多核cpu,為了在處理數(shù)據(jù)的時(shí)候保持緩存有效性,因此一個(gè)cpu需要數(shù)據(jù)而且該數(shù)據(jù)不在自身的cache中的時(shí)候,會同時(shí)向其他的cpu緩存和主存求取。如果其他的cpu緩存中有數(shù)據(jù),則使用該數(shù)據(jù),這樣就保證了不會使用到主存中的錯(cuò)誤的尚未更新的舊數(shù)據(jù)。

而各個(gè)cpu的內(nèi)部緩存依靠MESI緩存一致性協(xié)議來進(jìn)行協(xié)調(diào)。以此保證各個(gè)Cpu看到的內(nèi)容是一致的。

Store buffer

如果一個(gè)Cpu要寫出一個(gè)數(shù)據(jù),但是此時(shí)這個(gè)數(shù)據(jù)不在自己的cacheline中,因?yàn)閏pu要向其他的cpu緩存發(fā)出read invalidate消息。等待其他的cpu返回read response和invalidate ack消息后,將數(shù)據(jù)寫入這個(gè)cacheline。這里就存在著時(shí)間的浪費(fèi),因?yàn)椴还芷渌腸pu返回的是什么數(shù)據(jù),本cpu都是要將它覆蓋的。而在等待的這段時(shí)間,cpu無事可干,只能空轉(zhuǎn)。為了讓cpu不至于空閑,因?yàn)樵O(shè)計(jì)了store buffer組件。store buffer是每個(gè)cpu獨(dú)享的寫入緩存空間,用于存儲對cacheline的寫入,而且速度比cacheline高一個(gè)數(shù)量級,但是容量非常少。

但是store buffer會產(chǎn)生在單核上的讀寫不一致問題。下面是模擬

a = 1;
b = a+1;
assert b==2;

假設(shè)a不在本cpu的cacheline中。在其他cpu的cacheline中,值為0.會有如下的步驟

序號 操作內(nèi)容
1 發(fā)現(xiàn)a的地址不在本cpu的cacheline中,向其他的cpu發(fā)送read invalidate消息
2 將數(shù)據(jù)寫入store buffer中
3 收到其他cpu響應(yīng)的read response和invalidate ack消息
4 執(zhí)行b=a+1,因?yàn)閍這個(gè)時(shí)候已經(jīng)在cacheline中,讀取到值為0,加1后為1,寫入到b中
5 執(zhí)行assert b==2 失敗,因?yàn)閎是1
6 store buffer中的值刷新到a的cacheline中,修改a的值為1,但是已經(jīng)太晚了

為了避免這個(gè)問題,所以對于store buffer的設(shè)計(jì)中增加一個(gè)策略叫做store forwarding。就是說cpu在讀取數(shù)據(jù)的時(shí)候會先查看store buffer,如果store buffer中有數(shù)據(jù),直接用store buffer中的。這樣,也就避免了使用錯(cuò)誤數(shù)據(jù)的問題了。
store forwarding可以解決在單線程中的數(shù)據(jù)不一致問題,但是store buffer所帶來的復(fù)雜性遠(yuǎn)不止如此。在多線程環(huán)境下,會有其他的問題。下面是模擬代碼

public void set(){
  a=1;
  b=1;
}
public void print(){
 while(b==0)
 ;
 assert a==1;
}

假設(shè)a和b的值都是0,其中b在cpu0中,a在cpu1中。cpu0執(zhí)行set方法,cpu1執(zhí)行print方法。

序號 cpu0的步驟(執(zhí)行set) cpu1的步驟(執(zhí)行print)
1 想寫入a=1,但是由于a不在自身的cacheline中,向cpu1發(fā)送read invalidate消息 執(zhí)行while(b==0),由于b不在自身的cacheline中,向cpu0發(fā)送read消息
2 向store buffer中寫入a=1 等待cpu0響應(yīng)的read response消息
3 b在自身的cacheline中,并且此時(shí)狀態(tài)為M或者E,寫入b=1 等待cpu0響應(yīng)的read response消息
4 收到cpu1的read請求,將b=1的值用read response消息傳遞,同時(shí)將b所在的cacheline修改狀態(tài)為s 等待cpu0響應(yīng)的read response消息
5 等待cpu1的read response和invalidate ack消息 收到cpu0的read response消息,將b置為1,因此程序跳出循環(huán)
6 等待cpu1的read response和invalidate ack消息 因?yàn)閍在自身的cacheline中,所以讀取后進(jìn)行比對。assert a==1失敗。因?yàn)榇藭r(shí)a在自身cacheline中的值還是0,而且該cacheline尚未失效
7 等待cpu1的read response和invalidate ack消息 收到cpu0發(fā)送的read invalidate消息,將a所在的cacheline設(shè)置為無效,但是 為時(shí)已晚,錯(cuò)誤的判斷結(jié)果已經(jīng)產(chǎn)生了
8 收到cpu1響應(yīng)的read response和invalidate ack消息,將store buffer中的值寫入cacheline中

通過上面的例子可以看到,在多核系統(tǒng)中,store buffer的存在讓程序的結(jié)果與我們的預(yù)期不相符合。上面的程序中,由于store buffer的存在,所以在cacheline中的操作順序?qū)嶋H上先b=1然后a=1。就好像操作被重排序一樣(重排序這個(gè)詞在很多文章中都有,但是定義不詳,不好理解。實(shí)際上直接理解store buffer會簡單很多)。為了解決這樣的問題,cpu提供了一些操作指令,來幫助我們避免這樣的問題。這樣的指令就是內(nèi)存屏障(英文fence,也翻譯叫做柵欄)。來看下面的代碼

public void set(){
  a=1;
  smp_mb();
  b=1;
}
public void print(){
 while(b==0)
 ;
 assert a==1;
}

smb_mb()就是內(nèi)存屏障指令,英文memory barries。它的作用,是在后續(xù)的store動(dòng)作之前,將sotre buffer中的內(nèi)容刷新到cacheline。這個(gè)操作的效果是讓本地的cacheline的操作順序和代碼的順序一致,也就是讓其他cpu觀察到的該cpu的cacheline操作順序被分為smp_mb()之前和之后。要達(dá)到這個(gè)目的有兩種方式

  • 遇到smp_mb()指令時(shí),暫停cpu執(zhí)行,將當(dāng)前的store_buffer全部刷新到cacheline中,完成后cpu繼續(xù)執(zhí)行
  • 遇到smp_mb()指令時(shí),cpu繼續(xù)執(zhí)行,但是所有后續(xù)的store操作都進(jìn)入到了store buffer中,直到store buffer之前的內(nèi)容都被刷新到cacheline,即使此時(shí)需要store的內(nèi)容的cacheline是M或者E狀態(tài),也只能先寫入store buffer中。這樣的策略,既可以提升cpu效率,也保證了正確性。當(dāng)之前store buffer的內(nèi)容被刷新到cacheline完成后,后面新增加的內(nèi)容也會有合適的時(shí)機(jī)刷新到cacheline。把store buffer想象成一個(gè)FIFO的隊(duì)列就可以了。

下面來看,當(dāng)有了smp_mb()之后,程序的執(zhí)行情況。所有的初始假設(shè)與上面相同。

序號 cpu0的步驟(執(zhí)行set) cpu1的步驟(執(zhí)行print)
1 想寫入a=1,但是由于a不在自身的cacheline中,向cpu1發(fā)送read invalidate消息 執(zhí)行while(b==0),由于b不在自身的cacheline中,向cpu0發(fā)送read消息
2 向store buffer中寫入a=1 等待cpu0響應(yīng)的read response消息
3 遇到smp_mb(),等待直到可以將store buffer中的內(nèi)容刷新到cacheline 等待cpu0響應(yīng)的read response消息
4 等待直到可以將store buffer中的內(nèi)容刷新到cacheline 收到cpu0發(fā)來的read invalidate消息,發(fā)送a=0的值,同時(shí)將自身a所在的cacheline修改為invalidate狀態(tài)
5 收到cpu1響應(yīng)的read response和invalidate ack消息,將a=0的值設(shè)置到cacheline,隨后store buffer中a=1的值刷新到cacheline,設(shè)置cacheline狀態(tài)為M 等待cpu0響應(yīng)的read response消息
6 由于b就在自身的cacheline中,并且狀態(tài)為M或者E,設(shè)置值為b=1 等待cpu0響應(yīng)的read response消息
7 收到cpu1的read請求,將b=1的值傳遞回去,同時(shí)設(shè)置該cacheline狀態(tài)為s 等待cpu0響應(yīng)的read response消息
8 收到cpu0的read response信息,將b設(shè)置為1,程序跳出循環(huán)
9 由于a所在的cacheline被設(shè)置為invalidate,因此向cpu0發(fā)送read請求
10 收到cpu1的read請求,以a=1響應(yīng),并且將自身的cacheline狀態(tài)修改為s 等待cpu0的read response響應(yīng)
11 收到read response請求,將a設(shè)置為1,執(zhí)行程序判斷,結(jié)果為真

可以看到,在有了內(nèi)存屏障之后,程序的真實(shí)結(jié)果就和我們的預(yù)期結(jié)果相同了。

invalidate queue

使用了store buffer后,cpu的store性能會提升很多。然后store buffer的容量是很小的(越快的東西,成本就越高,一定就越。,cpu以中等的頻率填充store buffer。如果不幸發(fā)生比較多的cache miss,那么很快store buffer就被填滿了,cpu只能等待。又或者程序中調(diào)用了smp_mb()指令,這樣后續(xù)的操作都只能進(jìn)入store buffer,而不管相關(guān)cacheline是否處于M或者E狀態(tài)。

store buffer很容易滿的原因是因?yàn)槭盏狡渌鹀pu的invalidate ack的速度太慢。而cpu發(fā)送invalidate ack的速度太慢是因?yàn)閏pu要等到將對應(yīng)的cacheline設(shè)置為invalidate后才能發(fā)送invalidate ack。有的時(shí)候太多invalidate請求,cpu的處理速度就跟不上。為了加速這個(gè)流程,硬件設(shè)計(jì)者設(shè)計(jì)了invaldate queue來加速這個(gè)過程。收到的invalidate請求先放入invalidate queue,然后之后立刻響應(yīng)invalidate ack消息。而cpu可以在隨后慢慢的處理這些invalidate消息。當(dāng)然,這里必須不能太慢。也就是說,cpu實(shí)際上給出了一個(gè)承諾,如果一個(gè)invalidatge請求在invalidate queue中,那么對于這個(gè)請求相關(guān)的cacheline,在該請求被處理完成前,cpu不會再發(fā)送任何與該cacheline相關(guān)的MESI消息。在有了store buffer和invalidate queue后,cpu的處理速度又可以更高。下面是結(jié)構(gòu)圖。

但是在引入了invalidate queue又會導(dǎo)致另外一個(gè)問題。下面先來看代碼

public void set(){
  a=1;
  smp_mb();
  b=1;
}
public void print(){
 while(b==0)
 ;
 assert a==1;
}

代碼與上面的例子相同,但是初始條件不同了。這次a同時(shí)存在于cpu0和cpu1之中,狀態(tài)為s。b是cpu0獨(dú)享,狀態(tài)為E或者M(jìn)。

序號 cpu0的步驟(執(zhí)行set) cpu1的步驟(執(zhí)行print)
1 想寫入a=1,但是由于a的狀態(tài)是s,向cpu1發(fā)送invalidate消息 執(zhí)行while(b==0),由于b不在自身的cacheline中,向cpu0發(fā)送read消息
2 向store buffer中寫入a=1 收到cpu0的invalidate消息,放入invalidate queue,響應(yīng)invalidate ack消息。
3 遇到smp_mb(),等待直到可以將store buffer中的內(nèi)容刷新到cacheline。立刻收到cpu0的invalidate ack,將store buffer中的a=1寫入到cacheline,并且修改狀態(tài)為M 等待cpu0響應(yīng)的read response消息
4 由于b就在自己的cacheline中,寫入b=1,修改狀態(tài)為M 等待cpu0響應(yīng)的read response消息
5 收到cpu1響應(yīng)的read請求,將b=1作為響應(yīng)回傳,同時(shí)將cacheline的狀態(tài)修改為s。 等待cpu0響應(yīng)的read response消息
6 收到read response,將b=1寫入cacheline,程序跳出循環(huán)
7 由于a所在的cacheline還未失效,load值,進(jìn)行比對,assert失敗
8 cpu處理invalidate queue的消息,將a所在的cacheline設(shè)置為invalidate,但是已經(jīng)太晚了

上面的例子,看起來就好像第一個(gè)一樣,仍然是b=1先生效,a=1后生效。導(dǎo)致了cpu1執(zhí)行的錯(cuò)誤。就好像內(nèi)存操作”重排序”一樣(個(gè)人不太喜歡內(nèi)存操作重排序這個(gè)術(shù)語,因?yàn)閷?shí)際上并不是重新排序的問題,而是是否可見的問題。但是用重排序這樣的詞語,反而不好理解。但是很多書都是用是了這個(gè)詞語,大家可以有自己的理解。但是還是推薦不要理會這些作者的抽象概念,直接了解核心)。其實(shí)這個(gè)問題的觸發(fā),就是因?yàn)閕nvalidate queue沒有在需要被處理的時(shí)候處理完成,造成了原本早該失效的cacheline仍然被cpu認(rèn)為是有效,出現(xiàn)了錯(cuò)誤的結(jié)果。那么只要讓內(nèi)存屏障增加一個(gè)讓invalidate queue全部處理完成的功能即可。

硬件的設(shè)計(jì)者也是這么考慮的,請看下面的代碼

public void set(){
  a=1;
  smp_mb();
  b=1;
}
public void print(){
 while(b==0)
 ;
 smp_mb();
 assert a==1;
}

a同時(shí)存在于cpu0和cpu1之中,狀態(tài)為s。b是cpu0獨(dú)享,狀態(tài)為E或者M(jìn)。

序號 cpu0的步驟(執(zhí)行set) cpu1的步驟(執(zhí)行print)
1 想寫入a=1,但是由于a的狀態(tài)是s,向cpu1發(fā)送invalidate消息 執(zhí)行while(b==0),由于b不在自身的cacheline中,向cpu0發(fā)送read消息
2 向store buffer中寫入a=1 收到cpu0的invalidate消息,放入invalidate queue,響應(yīng)invalidate ack消息。
3 遇到smp_mb(),等待直到可以將store buffer中的內(nèi)容刷新到cacheline。立刻收到cpu0的invalidate ack,將store buffer中的a=1寫入到cacheline,并且修改狀態(tài)為M 等待cpu0響應(yīng)的read response消息
4 由于b就在自己的cacheline中,寫入b=1,修改狀態(tài)為M 等待cpu0響應(yīng)的read response消息
5 收到cpu1響應(yīng)的read請求,將b=1作為響應(yīng)回傳,同時(shí)將cacheline的狀態(tài)修改為s。 等待cpu0響應(yīng)的read response消息
6 收到read response,將b=1寫入cacheline,程序跳出循環(huán)
7 遇見smp_mb(),讓cpu將invalidate queue中的消息全部處理完后,才能繼續(xù)向下執(zhí)行。此時(shí)將a所在的cacheline設(shè)置為invalidate
8 由于a所在的cacheline已經(jīng)無效,向cpu0發(fā)送read消息
9 收到read請求,以a=1發(fā)送響應(yīng) 收到cpu0發(fā)送的響應(yīng),以a=1寫入cacheline,執(zhí)行assert a==1.判斷成功

可以看到,由于內(nèi)存屏障的加入,程序正確了。

內(nèi)存屏障

通過上面的解釋和例子,可以看出,內(nèi)存屏障是是因?yàn)橛辛藄tore buffer和invalidate queue之后,被用來解決可見性問題(也就是在cacheline上的操作重排序問題)。內(nèi)存屏障具備兩方面的作用

  • 強(qiáng)制cpu將store buffer中的內(nèi)容寫入到cacheline中
  • 強(qiáng)制cpu將invalidate queue中的請求處理完畢

但是有些時(shí)候,我們只需要其中一個(gè)功能即可,所以硬件設(shè)計(jì)者們就將功能細(xì)化,分別是

  • 讀屏障: 強(qiáng)制cpu將invalidate queue中的請求處理完畢。也被稱之為smp_rmb
  • 寫屏障: 強(qiáng)制cpu將store buffer中的內(nèi)容寫入到cacheline中或者將該指令之后的寫操作寫入store buffer直到之前的內(nèi)容被寫入cacheline.也被稱之為smp_wmb
  • 讀寫屏障: 強(qiáng)制刷新store buffer中的內(nèi)容到cacheline,強(qiáng)制cpu處理完invalidate queue中的內(nèi)容。也被稱之為smp_mb

JMM內(nèi)存模型

在上面描述中可以看到硬件為我們提供了很多的額外指令來保證程序的正確性。但是也帶來了復(fù)雜性。JMM為了方便我們理解和使用,提供了一些抽象概念的內(nèi)存屏障。注意,下文開始討論的內(nèi)存屏障都是指的是JMM的抽象內(nèi)存屏障,它并不代表實(shí)際的cpu操作指令,而是代表一種效果。

  • LoadLoad Barriers
    該屏障保證了在屏障前的讀取操作效果先于屏障后的讀取操作效果發(fā)生。在各個(gè)不同平臺上會插入的編譯指令不相同,可能的一種做法是插入也被稱之為smp_rmb指令,強(qiáng)制處理完成當(dāng)前的invalidate queue中的內(nèi)容
  • StoreStore Barriers
    該屏障保證了在屏障前的寫操作效果先于屏障后的寫操作效果發(fā)生?赡艿淖龇ㄊ鞘褂smp_wmb指令,而且是使用該指令中,將后續(xù)寫入數(shù)據(jù)先寫入到store buffer的那種處理方式。因?yàn)檫@種方式消耗比較小
  • LoadStore Barriers
    該屏障保證了屏障前的讀操作效果先于屏障后的寫操作效果發(fā)生。
  • StoreLoad Barriers
    該屏障保證了屏障前的寫操作效果先于屏障后的讀操作效果發(fā)生。該屏障兼具上面三者的功能,是開銷最大的一種屏障。可能的做法就是插入一個(gè)smp_mb指令來完成。

內(nèi)存屏障在volatile關(guān)鍵中的使用

內(nèi)存屏障在很多地方使用,這里主要說下對于volatile關(guān)鍵字,內(nèi)存屏障的使用方式。

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

上面的內(nèi)存屏障方式主要是規(guī)定了在處理器級別的一些重排序要求。而JMM本身,對于volatile變量在編譯器級別的重排序也制定了相關(guān)的規(guī)則。可以用下面的圖來表示

volatile變量除了在編譯器重排序方面的語義以外,還存在一條約束保證。如果cpu硬件上存在類似invalidate queue的東西,可以在進(jìn)行變量讀取操作之前,會先處理完畢queue上的內(nèi)容。這樣就能保證volatile變量始終是讀取最新的最后寫入的值。

Happen-before

JMM為了簡化對編程復(fù)雜的理解,使用了HB來表達(dá)不同操作之間的可見性。HB關(guān)系在不同的書籍中有不同的表達(dá)。這里推薦一種比較好理解的。

A Happen before B,說明A操作的效果先于B操作的效果發(fā)生。這種偏序關(guān)系在單線程中是沒有什么作用的,因?yàn)閱尉程中,執(zhí)行效果要求和代碼順序一致。但是在多線程中,其可見性作用就非常明顯了。舉個(gè)例子,在線程1中進(jìn)行進(jìn)行a,b操作,操作存在hb關(guān)系。那么當(dāng)線程2觀察到b操作的效果時(shí),必然也能觀察到a操作的效果,因?yàn)閍操作Happen before b操作。

在java中,存在HB關(guān)系的操作一共有8種,如下。

  1. 程序次序法則,如果A一定在B之前發(fā)生,則happen before
  2. 監(jiān)視器法則,對一個(gè)監(jiān)視器的解鎖一定發(fā)生在后續(xù)對同一監(jiān)視器加鎖之前
  3. Volatie變量法則:寫volatile變量一定發(fā)生在后續(xù)對它的讀之前
  4. 線程啟動(dòng)法則:Thread.start一定發(fā)生在線程中的動(dòng)作前
  5. 線程終結(jié)法則:線程中的任何動(dòng)作一定發(fā)生在線程終結(jié)之前(其他線程檢測到這個(gè)線程已經(jīng)終止,從Thread.join調(diào)用成功返回,Thread.isAlive()返回false)
  6. 中斷法則:一個(gè)線程調(diào)用另一個(gè)線程的interrupt一定發(fā)生在另一線程發(fā)現(xiàn)中斷之前。
  7. 終結(jié)法則:一個(gè)對象的構(gòu)造函數(shù)結(jié)束一定發(fā)生在對象的finalizer之前
  8. 傳遞性:A發(fā)生在B之前,B發(fā)生在C之前,A一定發(fā)生在C之前。

使用HB關(guān)系,在多線程開發(fā)時(shí)就可以盡量少的避免使用鎖,而是直接利用hb關(guān)系和volatile關(guān)鍵字來達(dá)到信息傳遞并且可見的目的。

比如很常見的一個(gè)線程處理一些數(shù)據(jù)并且修改標(biāo)識位后,另外的線程檢測到標(biāo)識位發(fā)生改變,就接手后續(xù)的流程。此時(shí)如何保證前一個(gè)線程對數(shù)據(jù)做出的更改后一個(gè)線程全部可見呢。先來看下面的代碼例子

class VolatileExample {
    int a = 0;
    volatile boolean flag = false;

    public void writer() {
        a = 1;     //1
        flag = true;    //2
    }

    public void reader() {
        while(flag==false); //3
        int i=a; //4
    }
}

有兩個(gè)不同的線程分別執(zhí)行writer和reader方法,根據(jù)Hb規(guī)則,有如下的順序執(zhí)行圖。

這樣的順序,i讀取到的a的值就是最新的,也即是1.

參考文獻(xiàn)

  • jsr133文檔

標(biāo)簽: 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:SpringBoot | 第二十二章:定時(shí)任務(wù)的使用

下一篇:SpringBoot | 番外:使用小技巧合集