四、Volatile變量的使用
volatile變量的讀寫(xiě)對(duì)所有線程立即可見(jiàn)
只是讀和寫(xiě)一步,復(fù)雜的運(yùn)算不能保證對(duì)其他線程可見(jiàn),因?yàn)閺?fù)雜的運(yùn)算可能會(huì)被編譯成多條指令,JMM只保證,volatile變量從工作內(nèi)存寫(xiě)回到主存是對(duì)其他線程可見(jiàn)的。先看一個(gè)具體的例子。
static volatile int i = 0;
// -XX:+PrintGC
// -XX:+PrintGCDetails
// -Xms20m
// -Xmn10m
// -Xmx20m
// -XX:+UseSerialGC
// -XX:MaxTenuringThreshold=15
// -XX:-HandlePromotionFailure
// -XX:+PrintHeapAtGC
public static void main(String[] args) {
int a = i++;
}1234567891011121314
編譯后的指令
JMM只是能夠保證(并不一定能夠保證,但是一條字節(jié)碼的指令也是由若干機(jī)器指令完成的,但是能夠說(shuō)明問(wèn)題了)getstatic 和 putstatic volatile變量的時(shí)候是原子的,至于中間的一些列操作,并不能夠保證再次期間沒(méi)有其他線程對(duì)i操作生成臟數(shù)據(jù)。也就是,JMM保證get操作的值是當(dāng)前內(nèi)存中最新的,以及put之后內(nèi)存中i的對(duì)其他內(nèi)存可見(jiàn)。
禁止指令的重排序
這一點(diǎn),《java并發(fā)編程的藝術(shù)》一書(shū)中講的比較詳細(xì)。
重排序分為編譯器重排序和處理器重排序。為了實(shí)現(xiàn)volatile內(nèi)存語(yǔ)義,JMM會(huì)分別限制這兩種類型的重排序類型。JMM針對(duì)編譯器制定的volatile重排序規(guī)則見(jiàn)下表。
個(gè)人總結(jié)來(lái)說(shuō),
volatile的讀下面的任何操作都不能重排序到volatile讀操作的上方,volatile上面的普通讀寫(xiě)可重排序到下方。
volatile的寫(xiě)上面的任何操作都不能重排序到volatile寫(xiě)操作的下方,volatile下面的普通讀寫(xiě)可重排序到上方。
任何兩個(gè)volatile的讀寫(xiě)順序不能重排。
為了實(shí)現(xiàn)上述的volatile的內(nèi)存語(yǔ)義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類型的處理器重排序。(StoreStore等屏障的介紹見(jiàn)文章最后)
在每個(gè)volatile寫(xiě)操作的前面插入一個(gè)StoreStore屏障。
在每個(gè)volatile寫(xiě)操作的后面插入一個(gè)StoreLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障。
插入屏障后的效果見(jiàn)下圖
LoadLoad屏障用來(lái)禁止處理器把上面的volatile讀與下面的普通讀重排序。LoadStore屏障用來(lái)禁止處理器把上面的volatile讀與下面的普通寫(xiě)重排序。
StoreStore屏障可以保證在volatile寫(xiě)之前,其前面的所有普通寫(xiě)操作已經(jīng)對(duì)任意處理器可見(jiàn)了。這是因?yàn)镾toreStore屏障將保障上面所有的普通寫(xiě)在volatile寫(xiě)之前刷新到主內(nèi)存。
這里比較有意思的是,volatile寫(xiě)后面的StoreLoad屏障。此屏障的作用是避免volatile寫(xiě)與后面可能有的volatile讀/寫(xiě)操作重排序。因?yàn)榫幾g器常常無(wú)法準(zhǔn)確判斷在一個(gè)volatile寫(xiě)的后面是否需要插入一個(gè)StoreLoad屏障(比如,一個(gè)volatile寫(xiě)之后方法立即return)。為了保證能正確實(shí)現(xiàn)volatile的內(nèi)存語(yǔ)義,JMM在采取了保守策略:在每個(gè)volatile寫(xiě)的后面,或者在每個(gè)volatile讀的前面插入一個(gè)StoreLoad屏障。從整體執(zhí)行效率的角度考慮,JMM最終選擇了在每個(gè)volatile寫(xiě)的后面插入一個(gè)StoreLoad屏障。因?yàn)関olatile寫(xiě)-讀內(nèi)存語(yǔ)義的常見(jiàn)使用模式是:一個(gè)寫(xiě)線程寫(xiě)volatile變量,多個(gè)讀線程讀同一個(gè)volatile變量。當(dāng)讀線程的數(shù)量大大超過(guò)寫(xiě)線程時(shí),選擇在volatile寫(xiě)之后插入StoreLoad屏障將帶來(lái)可觀的執(zhí)行效率的提升。從這里可以看到JMM在實(shí)現(xiàn)上的一個(gè)特點(diǎn):首先確保正確性,然后再去追求執(zhí)行效率。
上面這段話引自《java并發(fā)編程的藝術(shù)》一書(shū),但是不是很明白,volatile的寫(xiě)前面的所有操作都不得拍到volatile寫(xiě)之后,為什么這里只加入了Store-Store屏障呢,這樣普通讀不就可以重拍到volatile寫(xiě)的下方了??????
如果,volatile讀的上面還有volatile讀,因?yàn)関olatile讀下面都會(huì)插入load-load屏障,所以兩者不會(huì)重排。如果volatile讀的上面還有volatile寫(xiě),volatile寫(xiě)后面加入了store-load,所以下面的volatile讀不能能與之重排序。
屏障介紹:
評(píng)論
查看更多