volatile 介紹
volatile 關鍵字只能用在 variable,不能用在 method 或是 class,其目的是告訴 Java compiler 或是 Thread,讀取 variable 的值都要從 main memory 去讀取。因此假如你想讓 int,boolean 等 variable 的讀值或是寫值的行為是 atomic,就可以使用 volatile。
從 Java 5 之後,宣告為 volatile 的 variable 就具有 "happens-before relationship",且可以防止 Java compiler 對程式碼做 reorder 來達到最佳化的效果。
用程式碼來解釋 volatile 的用法,比較簡單的範例就是 Singleton Pattern:
更進一步來說,因為 thread 會各自有一份 local variable,就算加了同步,第一個建立實體的 thread 也不會把值寫回 main memory,其他 thread 在讀值時也不會從 main memory 讀值,自然就會有同步的問題。
最後整理幾個重點:
How Volatile in Java works? Example of volatile keyword in Java
從 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 值還是舊的,因此就可能建立實體多次。
知道了 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");
}
}
- 可以解決 Singleton Pattern 的 double-checked locking 問題。
最後整理幾個重點:
- volatile 只能用在 variable
- 使用 volatile 的變數會有 happens-before relationship,因此可以減少 Memory Consistency Error。
- Java 5 以上的 volatile 才有上述的功用
- 除了 float 跟 double 以外的 primitive type 跟 reference type 變數,就算不用 volatile,讀寫也是 atomic 的。
- 沒有要跨 thread 溝通的話就可以不用使用 volatile。
- 最重要的一點,使用 volatile 不保證運算是 atomic。舉個簡單的例子:i++ 相當於 i = i + 1,這基本上是做兩件事,賦值跟做加 1 的運算。因此在 multi-thread 的情形還是可能拿到不正確的值。要防止這樣的情形最好還是用 Synchronized 或是 AtomicInteger 等方法。
參考資料:
留言
張貼留言