1. 程式人生 > 其它 >volatile 指令重排的理解

volatile 指令重排的理解

指令重排

記憶體可見性只是 volatile 的其中一個語義,它還可以防止 JVM 進行指令重排優化。

舉一個虛擬碼:

  1. int a=10 ;//1
  2. int b=20 ;//2
  3. int c= a+b ;//3

一段特別簡單的程式碼,理想情況下它的執行順序是: 1>2>3。但有可能經過 JVM 優化之後的執行順序變為了 2>1>3

可以發現不管 JVM 怎麼優化,前提都是保證單執行緒中最終結果不變的情況下進行的。

可能這裡還看不出有什麼問題,那看下一段虛擬碼:

private static Map<String,String> value ;
private static volatile boolean flag = fasle ;
//以下方法發生線上程 A 中 初始化 Map
public void initMap(){
   //耗時操作
   value = getMapValue() ;//1
   flag = true ;//2
}
//發生線上程 B中 等到 Map 初始化成功進行其他操作
public void doSomeThing(){
   while(!flag){
       sleep() ;
   }
   //dosomething
   doSomeThing(value);
}

這裡就能看出問題了,當 flag 沒有被 volatile 修飾時, JVM 對 1 和 2 進行重排,導致 value 都還沒有被初始化就有可能被執行緒 B 使用了。

所以加上 volatile 之後可以防止這樣的重排優化,保證業務的正確性。

指令重排的的應用

一個經典的使用場景就是雙重懶載入的單例模式了:

public class Singleton {
   private static volatile Singleton singleton;
   private Singleton() {
   }
   public static Singleton getInstance() {
       if (singleton == null) {
           synchronized (Singleton.class) {
               if (singleton == null) {
                   //防止指令重排
                   singleton = new Singleton();
               }
           }
       }
       return singleton;
   }
}

這裡的 volatile 關鍵字主要是為了防止指令重排。

如果不用 , singleton=newSingleton();,這段程式碼其實是分為三步:

  • 分配記憶體空間。(1)
  • 初始化物件。(2)
  • singleton 物件指向分配的記憶體地址。(3)

加上 volatile 是為了讓以上的三步操作順序執行,反之有可能第二步在第三步之前被執行就有可能某個執行緒拿到的單例物件是還沒有初始化的,以致於報錯。