內存屏障

內存屏障(Memory barrier)

爲什麼會有內存屏障

  • 每個CPU都會有自己的緩存(有的甚至L1,L2,L3),緩存的目的就是爲了提高性能,避免每次都要向內存取。但是這樣的弊端也很明顯:不能實時的和內存發生信息交換,分在不同CPU執行的不同線程對同一個變量的緩存值不同。
  • 用volatile關鍵字修飾變量可以解決上述問題,那麼volatile是如何做到這一點的呢?那就是內存屏障,內存屏障是硬件層的概念,不同的硬件平臺實現內存屏障的手段並不是一樣,java通過屏蔽這些差異,統一由jvm來生成內存屏障的指令。

內存屏障是什麼

  • 硬件層的內存屏障分爲兩種:Load BarrierStore Barrier即讀屏障和寫屏障。
  • 內存屏障有兩個作用:
  1. 阻止屏障兩側的指令重排序;
  2. 強制把寫緩衝區/高速緩存中的髒數據等寫回主內存,讓緩存中相應的數據失效。
  • 對於Load Barrier來說,在指令前插入Load Barrier,可以讓高速緩存中的數據失效,強制從新從主內存加載數據;
  • 對於Store Barrier來說,在指令後插入Store Barrier,能讓寫入緩存中的最新數據更新寫入主內存,讓其他線程可見。

java內存屏障

  • java的內存屏障通常所謂的四種即LoadLoad,StoreStore,LoadStore,StoreLoad實際上也是上述兩種的組合,完成一系列的屏障和數據同步功能。
  • LoadLoad屏障:對於這樣的語句Load1; LoadLoad; Load2,在Load2及後續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
  • StoreStore屏障:對於這樣的語句Store1; StoreStore; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
  • LoadStore屏障:對於這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
  • StoreLoad屏障:對於這樣的語句Store1; StoreLoad; Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能

volatile語義中的內存屏障

  • volatile的內存屏障策略非常嚴格保守,非常悲觀且毫無安全感的心態:

在每個volatile寫操作前插入StoreStore屏障,在寫操作後插入StoreLoad屏障;
在每個volatile讀操作前插入LoadLoad屏障,在讀操作後插入LoadStore屏障;

  • 由於內存屏障的作用,避免了volatile變量和其它指令重排序、線程之間實現了通信,使得volatile表現出了鎖的特性。

final語義中的內存屏障

  • 對於final域,編譯器和CPU會遵循兩個排序規則:
  1. 新建對象過程中,構造體中對final域的初始化寫入和這個對象賦值給其他引用變量,這兩個操作不能重排序;(廢話嘛)
  2. 初次讀包含final域的對象引用和讀取這個final域,這兩個操作不能重排序;(晦澀,意思就是先賦值引用,再調用final值)
  • 總之上面規則的意思可以這樣理解,必需保證一個對象的所有final域被寫入完畢後才能引用和讀取。這也是內存屏障的起的作用:
  • 寫final域:在編譯器寫final域完畢,構造體結束之前,會插入一個StoreStore屏障,保證前面的對final寫入對其他線程/CPU可見,並阻止重排序。
  • 讀final域:在上述規則2中,兩步操作不能重排序的機理就是在讀final域前插入了LoadLoad屏障。
  • X86處理器中,由於CPU不會對寫-寫操作進行重排序,所以StoreStore屏障會被省略;而X86也不會對邏輯上有先後依賴關係的操作進行重排序,所以LoadLoad也會變省略。




鏈接:https://www.jianshu.com/p/2ab5e3d7e510
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章