volatile,可見性,有序性
volatile的特性
- 可見性:對一個volatile變數的讀,總能獲取其他任意執行緒對該變數最後的寫入。
- 有序性:JMM會限制volatile變數相關的編譯器重排序和處理器重排序。
記憶體語義的的實現
1.可見性的實現基於volatile的讀取,寫入兩個操作的記憶體語義。
- volatile寫的記憶體語義:當寫入一個volatile變數的時候,JMM將執行緒工作記憶體中的該變數的值重新整理到主記憶體。
- volatile讀的記憶體語義:當讀取一個volatile變數的時候,JMM首先將該執行緒工作記憶體中的這個變數設定為無效,迫使該執行緒重新從主記憶體獲取最新的有效值。
- 兩者結合起來,就實現了,volatile變數的可見性,因為一個執行緒去讀取volatile變數的時候獲取的肯定是最新的值。
2.有序性的實現基於JMM針對編譯器制定的volatile重排序表
能否重排序 | – | 第二個操作 | – |
---|---|---|---|
第一個操作 | 普通讀/寫 | volatile讀 | volatile寫 |
普通讀/寫 | NO | ||
volatile讀 | NO | NO | NO |
volatile寫 | NO | NO |
3.最核心的部分還是記憶體屏障的使用,記憶體屏障實現了volatile讀寫的記憶體語義,也實現了重排序表
理解JMM如何實現volatile的兩層記憶體語義的關鍵是==記憶體屏障==。volatile的兩層記憶體語義都是使用記憶體屏障來實現的。
首先,對4中記憶體屏障的介紹: 記憶體屏障用於控制特定條件下的重排序和記憶體可見性問題。
- LoadLoad屏障:對於這樣的語句Load1; LoadLoad; Load2,在Load2及後續讀取操作要讀取的資料被訪問前,保證Load1要讀取的資料被讀取完畢。
- StoreStore屏障:對於這樣的語句Store1; StoreStore; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
- LoadStore屏障:對於這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的資料被讀取完畢。
- StoreLoad屏障:對於這樣的語句Store1; StoreLoad; Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。 在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種記憶體屏障的功能。
再看看JMM記憶體屏障的插入策略
- volatile寫操作前面插入一個StoreStore屏障
對比StoreStore屏障的定義,這裡的volatile寫是那個Store2,該屏障保證在volatile寫執行之前,Store1的寫入操作對其他處理器可見,那麼可以得出,該屏障不僅保證了Store1已經執行完畢(有序性),也保證了可見性。
- 每個volatile寫操作的後面插入一個StoreLoad屏障
對比StoreLoad屏障的定義,這裡的volatile寫是那個Store1,該屏障也保證了有序性和可見性。其他都是類似的。
- 每個volatile讀操作後面插入一個LoadLoad屏障。
- 每個volatile讀操作後面插入一個LoadStore屏障。
4.從CPU的角度,看看可見性如何實現
//假設有個volatile變數instance,對其進行寫操作
instance = new Singleton();
在X86處理器下看看其對應的彙編程式碼的一部分:
lock add1 $0*0
lock字首的指令在多核處理器下引發了兩件事:
- 當前處理器快取行的資料寫回系統記憶體
- 使其他處理器快取行中快取的該記憶體地址的資料無效