指令重排序分爲三種,分別爲編譯器優化重排序、指令級並行重排序、內存系統重排序。如圖所示,後面兩種爲處理器級別(即爲硬件層面)。html
// CPU1執行如下操做
a = 1; int i = b; // CPU2執行下面操做 b = 1; int j = a;
其執行圖以下:
從上面圖中咱們能夠看到,對於CPU來講,先將a = 1寫入緩存在讀取變量b,事後在寫入a到主內存,而這個操做從表面上看就變成了先讀取變量b,在寫入a到主內存,也就是發生了重排序,因此才說這爲僞重排序。緩存
而從上面咱們也能夠看出,因爲CPU1和2寫入的時機不一樣,最終可能致使讀到的(a,b)變量有四種狀況,分別是(0,0),(0,1),(1,0),(1,1)。例如,在兩個緩存未寫入主內存的時候就進行變量讀取,這時候讀到的就爲(0,0),其餘狀況類推。因此Java在實現內存模型的時候會禁止特定類型的重排序。性能
as-if-serial語義:這是重排序都須要遵循的規則,其大體意思就是在單線程中,只要不改變程序的最終執行結果,那麼爲了提高性能能夠改變指令執行的順序。優化
在編譯器方面使用volatile關鍵字能夠禁止指令重排序,而在硬件方面實現禁止指令重排序的則是內存屏障。其中包括硬件層原本就有的LoadBarriers和StoreBarriers 和JVM封裝實現的四種內存屏障。spa
內存屏障分爲兩種,LoadBarriers和StoreBarriers。線程
i = a; LoadBarriers; // ..其餘操做
如上僞代碼中,在執行其餘操做以前必須保證a的變量從主內存中讀取而且刷新到緩存中。code
a = 1; b = 2; c = 3; StoreBarriers; // ..其餘操做
如上僞代碼中,保證在其餘操做以前,寫入緩存中的a,b,c三個變量同步到主內存中,而且其餘線程能夠觀察到變量的變化。htm
... int i = a; LoadLoad; int j = b;
在這段代碼中,在int j = b以及後面的Load操做中,都能見到int i = a的操做,也就是int i = a先於後面的讀取操做。即,禁止int i = a和以後的讀操做重排序。blog
int i = a; LoadStore b = 1;
// int i = a對於b = 1及以後的store操做都可見。
咱們都知道volatile關鍵字有兩個語義:排序
其中JVM對其禁止指令重排序在硬件層面的實現就是經過在volatile修飾的變量先後插入內存屏障。volatile變量的內存屏障規則以下:
在每一個volatile寫操做前插入StoreStore屏障,在寫操做後插入StoreLoad屏障;
在每一個volatile讀操做前插入LoadLoad屏障,在讀操做後插入LoadStore屏障;
而在編譯器方面則是由於對於volatile變量內存中的六種操做會有特殊的規則,能夠看看個人另外一篇文章——淺談內存模型,裏面介紹了volatile兩種語義的原理,同時也說明了volatile關鍵字沒有原子性的緣由。
文章如有不正之處,還望指出,在此多謝!
原文出處:https://www.cnblogs.com/zhangweicheng/p/11674660.html