1. 程式人生 > >JAVA 基礎系列之 重排序和Volatile

JAVA 基礎系列之 重排序和Volatile

重排序

在執行程式時,編輯器和處理器會對指令進行重排序,重排序分為:

  1. 編譯器重排序:在不改變程式碼語義的情況下,優化效能而改變了程式碼執行順序;
  2. 指令並行的重排序:處理器採用並行技術使多條指令重疊執行,在不存在資料依賴的情況下,改變機器指令的執行順序;
  3. 記憶體系統的重排序:使用快取和讀寫緩衝區時,載入和儲存可能是亂序執行。

比如現在有一段程式碼如下:

// 程式碼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最常見的應用場景

  1. 單例雙重校驗
class Singleton {
	private volatile static Singleton instance = null;
	private
Singleton{} public static Singleton getInstance() { if(instance == null) { // 保證不能重排序 synchronized (Singleton.class) { instance = new Singleton(); } } } }
  1. 標記狀態
volatile boolean shutdownRequested;

public void shutdown() { this.shutdownRequested = true; }

public void doWork() {
	while (!shutdownRequested) { // do shuff }
}
  1. java 的執行緒安全類也使用了Volatile,例如: java.util.concurrent.atomic, java.util.concurrent包下的類.

volatile 容易混淆的點:它只是保證了資料的可見性,而非原子性, 而atomicInteger 如何保證原子的呢,是因為在對volatile 值進行修改的時候,會去比對主記憶體中的值,判斷值是否被其他執行緒修改過,在進行修改