JAVA 基礎系列之 重排序和Volatile
阿新 • • 發佈:2018-12-12
重排序
在執行程式時,編輯器和處理器會對指令進行重排序,重排序分為:
- 編譯器重排序:在不改變程式碼語義的情況下,優化效能而改變了程式碼執行順序;
- 指令並行的重排序:處理器採用並行技術使多條指令重疊執行,在不存在資料依賴的情況下,改變機器指令的執行順序;
- 記憶體系統的重排序:使用快取和讀寫緩衝區時,載入和儲存可能是亂序執行。
比如現在有一段程式碼如下:
// 程式碼1
a = 1;
// 程式碼2
b = 1;
編譯器和處理器為了提高並行度,可以將程式碼1和2調整順序,即先執行程式碼1和程式碼2. 但是若有如下情況:
// 程式碼3
a = 1;
// 程式碼4
b = a;
這種情況因為程式碼3和4存在資料依賴和引用關係,存在hanpens-before關係,處理器和編輯器會遵守 as-if-serial原則,不會調整執行順序。
as-if-serial 原則:
不可以調整會導致執行結果改變的程式碼順序(單執行緒)hanpens-before:
指前一個操作對後一個操作可見,並不是前一個操作必須在後一個操作之前執行
當存在控制依賴時,編譯器和處理器會採用 猜測執行機制來提高並行度,如下程式碼:
a = 1;
flag = true;
if (flag) {// 程式碼5
a *= 2;// 程式碼 6
}
程式碼5和6不存在資料依賴,可能會重排,處理器和編譯器會先將程式碼6的執行結果放在緩衝區,等執行程式碼5之後,將緩衝區的結果直接賦值給a。 若要限制重排序,可以使用volatile關鍵字修飾變數。
volatile 限制重排序
volatile 會在讀的前後加入loadload 屏障和loadStore屏障,在寫後前後加入storestore和StoreLoad屏障。如下示意圖: 小結:
- 當第一個操作是Volatile讀時,不管第二個操作是什麼,都不能重排序
- 當第一個操作是Volatile寫時,第二個操作是Volatile讀或寫,不能重排序;
- 當第一個操作是普通讀寫,第二個操作是Volatile寫時,不能重排序。
Volatile最常見的應用場景
- 單例雙重校驗
class Singleton {
private volatile static Singleton instance = null;
private Singleton{}
public static Singleton getInstance() {
if(instance == null) { // 保證不能重排序
synchronized (Singleton.class) {
instance = new Singleton();
}
}
}
}
- 標記狀態
volatile boolean shutdownRequested;
public void shutdown() { this.shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) { // do shuff }
}
- java 的執行緒安全類也使用了Volatile,例如: java.util.concurrent.atomic, java.util.concurrent包下的類.
volatile 容易混淆的點:
它只是保證了資料的可見性,而非原子性,
而atomicInteger 如何保證原子的呢,是因為在對volatile 值進行修改的時候,會去比對主記憶體中的值,判斷值是否被其他執行緒修改過,在進行修改