為什么需要內存屏障?
由於現代的操作系統都是多處理器.而每一個處理器都有自己的緩存,並且這些緩存並不是實時都與內存發生信息交換.這樣就可能出現一個cpu上的緩存數據與另一個cpu上的緩存數據不一致的問題.而這樣在多線程開發中,就有可能導致出現一些異常行為. 而操作系統底層為了這些問題,提供了一些內存屏障用以解決這樣的問題
內存屏障的作用
1. 阻止屏障兩邊的指令重排序
2. 強制把寫緩沖區/高速緩存中的臟數據等寫回主內存,讓緩存中相應的數據失效(意思就是確保讀到的數據都是內存中的最新的數據,確保數據的有效性)
內存屏障的分類
硬件層提供了一系列的內存屏障 memory barrier / memory fence(Intel的提法)來提供一致性的能力。拿X86平台來說,有幾種主要的內存屏障
1. lfence,是一種Load Barrier 讀屏障。在讀指令前插入讀屏障,可以讓高速緩存中的數據失效,重新從主內存加載數據
2. sfence, 是一種Store Barrier 寫屏障。在寫指令之后插入寫屏障,能讓寫入緩存的最新數據寫回到主內存
3. mfence, 是一種全能型的屏障,具備ifence和sfence的能力
4. Lock前綴,Lock不是一種內存屏障,但是它能完成類似內存屏障的功能。Lock會對CPU總線和高速緩存加鎖,可以理解為CPU指令級的一種鎖。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。
Lock前綴實現了類似的能力,
1. 它先對總線/緩存加鎖,然后執行后面的指令,最后釋放鎖后會把高速緩存中的臟數據全部刷新回主內存。
2. 在Lock鎖住總線的時候,其他CPU的讀寫請求都會被阻塞,直到鎖釋放。Lock后的寫操作會讓其他CPU相關的cache line失效,從而從新從內存加載最新的數據。這個是通過緩存一致性協議做的。
內存屏障使用例子
- LoadLoad屏障(讀讀屏障):對於這樣的語句Load1; LoadLoad; Load2,在Load2及后續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
- StoreStore屏障(寫寫屏障):對於這樣的語句Store1; StoreStore; Store2,在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
- LoadStore屏障(讀寫屏障):對於這樣的語句Load1; LoadStore; Store2,在Store2及后續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
- StoreLoad屏障(寫讀屏障):對於這樣的語句Store1; StoreLoad; Load2,在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能。
java中內存屏障的常見例子
- 通過 Synchronized關鍵字包住的代碼區域,當線程進入到該區域讀取變量信息時,保證讀到的是最新的值.這是因為在同步區內對變量的寫入操作,在離開同步區時就將當前線程內的數據刷新到內存中,而對數據的讀取也不能從緩存讀取,只能從內存中讀取,保證了數據的讀有效性.這就是插入了StoreStore屏障
- 使用了volatile修飾變量,則對變量的寫操作,會插入StoreLoad屏障.
- 其余的操作,則需要通過Unsafe這個類來執行.