讀寫volatile變量就像是訪問一個同步塊一樣,是原子的且是可見的,總是能訪問到最新的值。
原子性
讀寫volatile變量是原子操作,但讀寫變量不就是一條指令的事嗎(mov、ldr),難道這還可分?沒錯絕大多數變量讀寫都是原子的,除了在32位JVM下對long、double的讀寫,就不是原子的。這是因為在32位下,總線寬度就只有32bit,對64位數據的讀寫需要分兩次進行,依次讀寫高低32位。但是讀寫volatile變量由於使用了LOCK前綴指令,鎖住了內存,所以即使是64位的數據也是原子的。
讀寫volatile變量是原子的,包括64位的long和double
實現原子性
實現64位的原子性,需要在讀寫volatile變量時,使用Lock前綴指令,其作用有:
- 鎖住該內存地址,直到讀完/寫完,保證64位變量讀寫原子性。少量處理器是使用鎖總線實現的,相比鎖內存,其開銷更大,鎖總線期間,所有處理器都不能操作主存外存。
- 將寫緩存刷新到主存,保證可見性
- 禁止該指令與前面和后面的讀寫指令重排序,保證happens-before關系
可見性
happens-before中定義了:寫volatile變量,happens-before后面任意一個讀這個volatile變量的操作
這意味着volatile變量在多線程間具有可見性,從源碼到Runtime發生的重排序指出重排序破壞了可見性。為實現volatile的可見性,讀寫volatile時則需要禁止重排序,那么需要禁止編譯器重排序和處理器重排序
happens-before關系
happens-before規則
- 程序順序規則:在一個線程中,前面的操作happens-before后面的操作
- volatile寫-讀規則:寫volatile變量,happens-before后面任意一個讀這個volatile變量的操作
- 傳遞性規則:A happens-before B,B happens-before C,則A happens-before C
從這段代碼看看happens-before關系,線程A先執行store(),線程B后執行load()
int value = 0;
volatile boolean finish = false;
void store(){
value = 1; //A
read(value); //B
finish = true; //C
value = 2; //D
read(value); //E
}
void load(){
value = 3; //F
read(value); //G
while(!finish); //H
assert value == 1; //I
value = 4; //J
}
①~⑧是程序順序規則,⑨是volatile寫-讀規則,淺色的是傳遞性規則,后面詳細解釋這些關系。

從happends-before規則分析可見性
①~⑧是根據程序順序規則得出的,程序順序規則前提是僅考慮本線程的可見性,那么就不需要考慮多個處理器引發的緩存不一致問題,不需要考慮內存系統重排序,所以不需要用到內存屏障。這樣就很簡單了,只要保證其在單線程內運行結果不變即可,只要保證編譯器、處理器不重排數據依賴的指令。
⑨是根據volatile域寫-讀規則得出的得出:C happens-before H。也就是線程A寫volatile happens-before 線程B讀volatile。
再根據傳遞性規則得出:ABC happens-before H 。也就是線程A寫volatile及其之前的操作 happens-before 線程B讀volatile。
再根據傳遞性規則得出:ABC happens-before HIJ 。最終得出線程A寫volatile及其之前的操作 happens-before 線程B讀volatile及其后續操作
這樣來看,寫volatile時,需要馬上將本地內存刷新到主存中去。讀volatile時,需要將本地內存中共享變量設為無效狀態,重新從主存中讀。
編譯器層面實現可見性

可以看到:
- 寫volatile變量時,無論前一個操作是什么,都不能重排序(①~④happens-before)
- 讀volatile變量時,無論后一個操作是什么,都不能重排序(⑤~⑧happens-before)
- 當先寫volatile變量,后讀volatile變量時,不能重排序(⑨happens-before)
處理器層面實現可見性
根據前面的出來的可見性:線程A寫volatile及其之前的操作 happens-before 線程B讀volatile及其后續操作。
可以看到這個可見性是在多線程間的,所以要避免內存系統重排序,需要使用JMM提供的內存屏障

先給可見性拆分,方便從最簡單的開始實現:
- 線程A寫volatile happens-before 線程B讀volatile
- 線程A寫volatile及其之前的操作 happens-before 線程B讀volatile
- 線程A寫volatile及其之前的操作 happens-before 線程B讀volatile及其后續操作
實現可見性:
- 對第一級可見性,可以在寫volatile之后加StoreLoad Barrier,也可以在讀volatile之前加StoreLoad Barrier。選擇哪種?在實際開發中,常用的模式是一個寫線程,多個讀線程,典型的有生產者消費者模式,所以在寫volatile后加StoreLoad Barrier,會大大減少執行屏障的次數,比后者的性能要好。
- 對第二級可見性,在寫volatile之前加上StoreStore Barrier,可以保證寫volatile之前,其之前的所有操作結果已經可見。不用LoadStore Barrier的原因是:讀操作並不會改變操作結果。
- 對第三級可見性,實際上是保證讀volatile后續操作會不會和讀volatile重排序。那么就在讀volatile后面加LoadLoad Barrier,這樣保證讀volatile在其后續讀操作之前執行,這樣的話 線程A 對 讀volatile的后續讀操作也可見。同理為了使線程A 對 讀volatile后續寫操作可見,在讀volatile后加上LoadStore Barrier。
綜上所述:
- 寫volatile之前,StoreStore Barrier
- 寫volatile之后,StoreLoad Barrier
- 讀volatile之后,LoadLoad Barrier 和 LoadStore Barrier
在剛才的例子上添加內存屏障,實現happens-before關系。
int value = 0;
volatile boolean finish = false;
void store(){
value = 1; //A
read(value); //B
storeStoreBarrier();
finish = true; //C
storeLoadBarrier();
value = 2; //D
read(value); //E
}
void load(){
value = 3; //F
read(value); //G
while(!finish); //H
loadLoadBarrier();
loadStoreBarrier();
assert value == 1; //I
value = 4; //J
}
