volatile,可見性,有序性

railgun發表於2019-01-19

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字首的指令在多核處理器下引發了兩件事:

  • 當前處理器快取行的資料寫回系統記憶體
  • 使其他處理器快取行中快取的該記憶體地址的資料無效

相關文章