內(nèi)存屏障(Memory barrier)
為什么會有內(nèi)存屏障
- 每個(gè)CPU都會有自己的緩存(有的甚至L1,L2,L3),緩存的目的就是為了提高性能,避免每次都要向內(nèi)存取。但是這樣的弊端也很明顯:不能實(shí)時(shí)的和內(nèi)存發(fā)生信息交換,分在不同CPU執(zhí)行的不同線程對同一個(gè)變量的緩存值不同。
- 用volatile關(guān)鍵字修飾變量可以解決上述問題,那么volatile是如何做到這一點(diǎn)的呢?那就是內(nèi)存屏障,內(nèi)存屏障是硬件層的概念,不同的硬件平臺實(shí)現(xiàn)內(nèi)存屏障的手段并不是一樣,java通過屏蔽這些差異,統(tǒng)一由jvm來生成內(nèi)存屏障的指令。
內(nèi)存屏障是什么
- 硬件層的內(nèi)存屏障分為兩種:
Load Barrier和Store Barrier即讀屏障和寫屏障。 - 內(nèi)存屏障有兩個(gè)作用:
- 阻止屏障兩側(cè)的指令重排序;
- 強(qiáng)制把寫緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫回主內(nèi)存,讓緩存中相應(yīng)的數(shù)據(jù)失效。
- 對于Load Barrier來說,在指令前插入Load Barrier,可以讓高速緩存中的數(shù)據(jù)失效,強(qiáng)制從新從主內(nèi)存加載數(shù)據(jù);
- 對于Store Barrier來說,在指令后插入Store Barrier,能讓寫入緩存中的最新數(shù)據(jù)更新寫入主內(nèi)存,讓其他線程可見。
java內(nèi)存屏障
- java的內(nèi)存屏障通常所謂的四種即
LoadLoad,StoreStore,LoadStore,StoreLoad實(shí)際上也是上述兩種的組合,完成一系列的屏障和數(shù)據(jù)同步功能。 - LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
- StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見。
- LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
- StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。在大多數(shù)處理器的實(shí)現(xiàn)中,這個(gè)屏障是個(gè)萬能屏障,兼具其它三種內(nèi)存屏障的功能
volatile語義中的內(nèi)存屏障
- volatile的內(nèi)存屏障策略非常嚴(yán)格保守,非常悲觀且毫無安全感的心態(tài):
在每個(gè)volatile寫操作前插入StoreStore屏障,在寫操作后插入StoreLoad屏障;
在每個(gè)volatile讀操作前插入LoadLoad屏障,在讀操作后插入LoadStore屏障;
- 由于內(nèi)存屏障的作用,避免了volatile變量和其它指令重排序、線程之間實(shí)現(xiàn)了通信,使得volatile表現(xiàn)出了鎖的特性。
final語義中的內(nèi)存屏障
- 對于final域,編譯器和CPU會遵循兩個(gè)排序規(guī)則:
- 新建對象過程中,構(gòu)造體中對final域的初始化寫入和這個(gè)對象賦值給其他引用變量,這兩個(gè)操作不能重排序;(廢話嘛)
- 初次讀包含final域的對象引用和讀取這個(gè)final域,這兩個(gè)操作不能重排序;(晦澀,意思就是先賦值引用,再調(diào)用final值)
- 總之上面規(guī)則的意思可以這樣理解,必需保證一個(gè)對象的所有final域被寫入完畢后才能引用和讀取。這也是內(nèi)存屏障的起的作用:
- 寫final域:在編譯器寫final域完畢,構(gòu)造體結(jié)束之前,會插入一個(gè)StoreStore屏障,保證前面的對final寫入對其他線程/CPU可見,并阻止重排序。
- 讀final域:在上述規(guī)則2中,兩步操作不能重排序的機(jī)理就是在讀final域前插入了LoadLoad屏障。
- X86處理器中,由于CPU不會對寫-寫操作進(jìn)行重排序,所以StoreStore屏障會被省略;而X86也不會對邏輯上有先后依賴關(guān)系的操作進(jìn)行重排序,所以LoadLoad也會變省略。