參考文檔:html
https://tech.meituan.com/java-memory-reordering.htmljava
http://0xffffff.org/2017/02/21/40-atomic-variable-mutex-and-memory-barrier/緩存
內存可見性:http://blog.csdn.net/ty_laurel/article/details/52403718app
1、什麼是重排序函數
重排序分爲2種優化
經過調整代碼中的指令順序,在不改變代碼語義的前提下,對變量訪問進行優化。從而儘量的減小對寄存器的讀取和存儲,並充分複用寄存器。可是編譯器對數據的依賴關係判斷只能在單執行流內,沒法判斷其餘執行流對競爭數據的依賴關係atom
流水線(Pipeline)和亂序執行是現代CPU基本都具備的特性。機器指令在流水線中經歷取指、譯碼、執行、訪存、寫回等操做。爲了CPU的執行效率,流水線都是並行處理的,在不影響語義的狀況下。處理器次序(Process Ordering,機器指令在CPU實際執行時的順序)和程序次序(Program Ordering,程序代碼的邏輯執行順序)是容許不一致的,即知足As-if-Serial特性。顯然,這裏的不影響語義依舊只能是保證指令間的顯式因果關係,沒法保證隱式因果關係。即沒法保證語義上不相關可是在程序邏輯上相關的操做序列按序執行spa
as-if-serial語義:.net
全部的動做均可覺得了優化而被重排序,可是必須保證它們重排序後的結果和程序代碼自己的應有結果是一致的。Java編譯器、運行時和處理器都會保證單線程下的as-if-serial語義線程
爲保證as-if-serial語義,Java異常處理機制也會爲重排序作一些特殊處理。例如在下面的代碼中,y = 0 / 0可能會被重排序在x = 2以前執行,爲了保證最終不致於輸出x = 1的錯誤結果,JIT在重排序時會在catch語句中插入錯誤代償代碼,將x賦值爲2,將程序恢復到發生異常時應有的狀態。這種作法的確將異常捕捉的邏輯變得複雜了,可是JIT的優化的原則是,盡力優化正常運行下的代碼邏輯,哪怕以catch塊邏輯變得複雜爲代價,畢竟,進入catch塊內是一種「異常」狀況的表現
public class Reordering { public static void main(String[] args) { int x, y; x = 1; try { x = 2; y = 0 / 0; } catch (Exception e) { } finally { System.out.println("x = " + x); } } }
重排序知足happen before原則
2、什麼是內存可見性
可見性:一個線程對共享變量值的修改,可以及時地被其餘線程看到
共享變量:若是一個變量在多個線程的工做內存中都存在副本,那麼這個變量就是這幾個線程的共享變量
Java內存模型(JMM)
Java內存模型(Java Memory Model)描述了Java程序中各類變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的底層細節。
全部的變量都存儲在主內存中。每一個線程都有本身獨立的工做內存,裏面保存該線程使用到的變量的副本(主內存中該變量的一份拷貝),如圖
兩條規定:
在這種模型下會存在一個現象,即緩存中的數據與主內存的數據並非實時同步的,各CPU(或CPU核心)間緩存的數據也不是實時同步的。這致使在同一個時間點,各CPU所看到同一內存地址的數據的值多是不一致
如何實現內存可見性:
要實現共享變量的可見性,必須保證兩點
1)synchronized實現可見性
synchronized可以實現:
原子性(同步)
可見性
JMM關於synchronized的兩條規定:
線程解鎖前對共享變量的修改在下次加鎖時對其餘線程可見
線程執行互斥代碼的過程
2)volatile實現可見性
volatile關鍵字:
可以保證volatile變量的可見性
不能保證volatile變量複合操做的原子
volatile如何實現內存的可見性:
在每一個volatile寫操做前插入StoreStore屏障,在寫操做後插入StoreLoad屏障
在每一個volatile讀操做前插入LoadLoad屏障,在讀操做後插入LoadStore屏障
線程寫volatile變量的過程:
線程讀volatile變量的過程:
synchronized vs volatile
3、內存屏障
內存屏障的做用:
硬件層的內存屏障分爲兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障
java內存屏障:
final語義中的內存屏障:
4、優化屏障
避免編譯器的重排序優化操做,保證編譯程序時在優化屏障以前的指令不會在優化屏障以後執行。這就保證了編譯時期的優化不會影響到實際代碼邏輯順序
優化屏障告知編譯器: 內存信息已經修改,屏障後的寄存器的值必須從內存中從新獲取 必須按照代碼順序產生彙編代碼,不得越過屏障