volatile 介紹

        volatile 關鍵字只能用在 variable,不能用在 method 或是 class,其目的是告訴 Java compiler 或是 Thread,讀取 variable 的值都要從 main memory 去讀取。因此假如你想讓 intboolean 等 variable 的讀值或是寫值的行為是 atomic,就可以使用 volatile。

        從 Java 5 之後,宣告為 volatile 的 variable 就具有 "happens-before relationship",且可以防止 Java compiler 對程式碼做 reorder 來達到最佳化的效果。

        用程式碼來解釋 volatile 的用法,比較簡單的範例就是 Singleton Pattern
public class Singleton {

    // 利用 volatile, 可以保證此變數的值是一致的
    private volatile static Singleton sInstance;

    private Singleton(){}

    public static Singleton getInstance()
    {
        // 只有第一次建立物件才會完整執行此段程式
        if(sInstance == null)
        {
            synchronized (Singleton.class)
            {
                // 進入同步區後再檢查一次,
                // 還是 null 才建立實體
                if(sInstance == null)
                {
                    sInstance = new Singleton();
                }
            }
        }
        return sInstance;
    }
}
        從上面的程式碼可以看到,假如 sInstance 沒宣告為 volatile 的話,當多個 thread 進入 getInstance() 的 synchronized 時,sInstance 雖然被其中一個 thread 建立實體了,但其他還卡在同步區塊的 thread,他們所保有的 sInstance 值還是舊的,因此就可能建立實體多次。

        更進一步來說,因為 thread 會各自有一份 local variable,就算加了同步,第一個建立實體的 thread 也不會把值寫回 main memory,其他 thread 在讀值時也不會從 main memory 讀值,自然就會有同步的問題。

        知道了 volatile 的原理,也要知道什麼時機適合使用才行。
  • 想讓讀取 float / double 變數是 atomic 時:float double 在 Java 是 64 bit,許多 platform 在寫值時是分成兩步,一次寫 32 bit。因此在 multi-thread 的情形很可能會讀到正寫到一半的值。
  • 可以當成輕量化的 Java 同步解法:因為 volatile 保證讀值一定要去 main memory 讀。
  • 防止 Java compiler 為程式做 reorder 或最佳化:以下面的程式來說,不加 volatile 的話,thread 每次的 iteration,因為 compiler 可能會把 isActive 的 cache value 清掉,且不會去 main memory 讀 isActive 的值,就可能會有無窮迴圈的問題。
private boolean isActive = thread;
public void printMessage() {
    while(isActive) {
        System.out.println("Thread is Active");
    }
}



        最後整理幾個重點:

  1. volatile 只能用在 variable
  2. 使用 volatile 的變數會有 happens-before relationship,因此可以減少 Memory Consistency Error。
  3. Java 5 以上的 volatile 才有上述的功用
  4. 除了 floatdouble 以外的 primitive type 跟 reference type 變數,就算不用 volatile,讀寫也是 atomic 的。
  5. 沒有要跨 thread 溝通的話就可以不用使用 volatile。
  6. 最重要的一點,使用 volatile 不保證運算是 atomic。舉個簡單的例子:i++ 相當於 i = i + 1,這基本上是做兩件事,賦值跟做加 1 的運算。因此在 multi-thread 的情形還是可能拿到不正確的值。要防止這樣的情形最好還是用 Synchronized 或是 AtomicInteger  等方法。
參考資料:

    How Volatile in Java works? Example of volatile keyword in Java

留言

這個網誌中的熱門文章

Java 泛型 (Generic) 的 Covariance, Contravariance, 以及 Invariance

transient 介紹

為什麼 GUI framework 大多都是 single-threaded (not thread safe)