[2011-1-1補充] volatile關鍵字
感謝馮大哥留言,在運用Double-Checked locking的作法時,在Java 5.0之後必須要加上volatile的修飾子在物件的變數宣告上,也就是變成:
- private volatile static Singleton instance;
這樣子的寫法。原因在於,如果不加上volatile這個關鍵字時,在Java中由
"順序"的問題,還是有可能會發生問題。最顯而易見的錯誤在於,當A執行緒進入getInstance()方法後,並且得到
if(instance==null)為true的結果,進入到同步區,要執行
Singleton()建構子內的敘述,但還沒有執行完時,執行緒B就進入getInstance()方法中,並且得到instance不是null的結果(因為在記憶體空間中已經配置的位置給instance了),如此一來就會造成執行緒B的物件內容並沒有成功的被指派,A和B之間就會有不同步的情形了。
詳細的過程可以參考這篇文章:
Double-checked Locking (DCL) and how to fix it
我覺得寫的還蠻清楚的,另外我也加了相關閱讀在最下方,歡迎大家參考,這幾篇對於Double Check Locking的機制都寫得蠻清楚的,值得一看囉。
==
在Singleton Pattern (單例模式) Part1中的【Lazy Instantization】方法可以說是最基礎的方式,但是在多執行緒的程式當中,這樣的寫法還是有可能會產生不同的實體,也就是這樣的寫法並不是一個thread-safe的方式,我們可以寫一個小程式來檢查看看:
public class SingletonTest extends Thread {
String threadName = "";
public SingletonTest(String name) {
threadName = name;
}
public void run() {
for(int i=0 ; i<10 ; i++) {
try {
Thread.sleep((int)500);
Singleton instance1 = Singleton.getInstance();
System.out.println(threadName + " ==> " + instance1.hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String [] a) {
Thread t1 = new SingletonTest("thread-1");
Thread t2 = new SingletonTest("thread-2");
t1.start();
t2.start();
}
}
兩個threads可能會產生相同的實體(相同的hashCode)或是不同的實體(但不一定每次都會發生),原因在於兩個threads可能同時呼叫
Singleton.getInstance()時,在進行
if(instance==null)判斷時,結果都可能會是true,以至於都產生一個新的實體,這樣就會違背Singleton Pattern的宗旨了,所以我們必須進一步的修改這個模式。
修改的想法是,當A、B兩個執行緒同時呼叫
Singleton.getInstance()這個方法來取得物件的實體時,必須要先等對方結束之後才呼叫,才不會造成問題。換句話說,
getInstance()這個方法在Java中只要加上
Synchronized這個修飾子就可以解決了,如此一來,整個getInstance()方法就變成一個同步等待區,可以避免多執行緒造成重複實體的問題。
public class Singleton {
private static Singleton instance;
private Singleton () { }
public static Synchronized Singleton getInstance() {
if(instance==null)
instance = new Singleton();
return instance;
}
}
上面提到,某個執行緒必須等到另外一個執行緒離開之後才能存取getInstance()這個method,當執行緒一多的時候,可以想像的會造成效率的低落。所以又有進一步的改良寫法,那就是採用
雙重上鎖(Double-Checked locking)的方式。
雙重上鎖的概念是,一開始先檢查instance這個變數是不是null,如果不是null代表之前已經實體化了,直接回傳就好,如果沒有實體化時,才會進入同步等待區域。實際撰寫上只要將上面的getInstance()方法改寫成以下的形式就可以了:
public class Singleton {
private volatile static Singleton instance;
private Singleton () { }
public static Singleton getInstance() {
if(instance==null) {
Synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
如此一來就可以解決多執行緒效率的問題囉!歡迎大家一起留言討論:)
【相關閱讀】
Double-checked locking and the Singleton pattern
Double-checked Locking (DCL) and how to fix it
The "Double-Checked Locking is Broken" Declaration