為什么會有內存屏障
- 每個CPU都會有自己的緩存(有的甚至L1,L2,L3),緩存的目的就是為了提高性能,避免每次都要向內存取。但是這樣的弊端也很明顯:不能實時的和內存發生信息交換,分在不同CPU執行的不同線程對同一個變量的緩存值不同。
- 用volatile關鍵字修飾變量可以解決上述問題,那么volatile是如何做到這一點的呢?那就是內存屏障,內存屏障是硬件層的概念,不同的硬件平台實現內存屏障的手段並不是一樣,java通過屏蔽這些差異,統一由jvm來生成內存屏障的指令。
內存屏障是什么
- 硬件層的內存屏障分為兩種:
Load Barrier
和Store Barrier
即讀屏障和寫屏障。 - 內存屏障有兩個作用:
- 阻止屏障兩側的指令重排序;
- 強制把寫緩沖區/高速緩存中的臟數據等寫回主內存,讓緩存中相應的數據失效。
- 對於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會遵循兩個排序規則:
- 新建對象過程中,構造體中對final域的初始化寫入和這個對象賦值給其他引用變量,這兩個操作不能重排序;
- 初次讀包含final域的對象引用和讀取這個final域,這兩個操作不能重排序;(意思就是先賦值引用,再調用final值)
- 總之上面規則的意思可以這樣理解,必需保證一個對象的所有final域被寫入完畢后才能引用和讀取。這也是內存屏障的起的作用:
- 寫final域:在編譯器寫final域完畢,構造體結束之前,會插入一個StoreStore屏障,保證前面的對final寫入對其他線程/CPU可見,並阻止重排序。
- 讀final域:在上述規則2中,兩步操作不能重排序的機理就是在讀final域前插入了LoadLoad屏障。
- X86處理器中,由於CPU不會對寫-寫操作進行重排序,所以StoreStore屏障會被省略;而X86也不會對邏輯上有先后依賴關系的操作進行重排序,所以LoadLoad也會變省略。
轉自: https://www.jianshu.com/p/2ab5e3d7e510
JMM模型
JMM 全稱是 Java Memory Model. 什么是 JMM 呢? 通過前面的分析發現,導致可見性問題的根本原因是緩存 以及重排序。 而 JMM 實際上就是提供了合理的禁用緩存 以及禁止重排序的方法。所以它最核心的價值在於解決可 見性和有序性。
它解決了 CPU 多級緩存、處理器優化、指令重排序 導致的內存訪問問題,保證了並發場景下的可見性。
需要注意
JMM 並沒有限制執行引擎使用處理器的寄 存器或者高速緩存來提升指令執行速度,也沒有限制編譯 器對指令進行重排序,也就是說在 JMM 中,也會存在緩存 一致性問題和指令重排序問題。只是 JMM 把底層的問題抽 象到 JVM 層面,再基於 CPU 層面提供的內存屏障指令, 以及限制編譯器的重排序來解決並發問題
JMM 抽象模型分為
主內存、工作內存;主內存是所有線程 共享的,一般是實例對象、靜態字段、數組對象等存儲在 堆內存中的變量。工作內存是每個線程獨占的,線程對變 量的所有操作都必須在工作內存中進行,不能直接讀寫主 內存中的變量,線程之間的共享變量值的傳遞都是基於主 內存來完成
Java 內存模型底層實現可以簡單的認為:通過內存屏障 (memory barrier)禁止重排序,即時編譯器根據具體的底層 體系架構,將這些內存屏障替換成具體的 CPU 指令。對 於編譯器而言,內存屏障將限制它所能做的重排序優化。 而對於處理器而言,內存屏障將會導致緩存的刷新操作。 比如,對於 volatile,編譯器將在 volatile 字段的讀寫操作 前后各插入一些內存.
JMM 是如何解決可見性有序性問題的
簡單來說,JMM 提供了一些禁用緩存以及進制重排序的方 法,來解決可見性和有序性問題。這些方法大家都很熟悉:
volatile、synchronized、final.等.
從源代碼到最終執行的指令,可能會經過三種重排序。

- 編譯器的重排序,JMM 提供了禁止特定類型的編譯器重排 序。
- 處理器重排序,JMM 會要求編譯器生成指令時,會插入內 存屏障來禁止處理器重排序