單例模式分爲懶漢式和餓漢式兩種,一種是以時間換空間,一種則是以空間換時間,而且餓漢式是具有線程安全,就不必過多討論。我們組要討論爲什麼餓漢式要進行雙重檢測??它又有什麼問題??
新手可能寫出下面的代碼
private Singleton() { } // 默認構造器
private static Singleton instance = null;// 延時加載
//每次運行都要創建實例因而需要加鎖保護
public static synchronized Singleton getIntance() {
if (instance == null) {
instance = new Singleton();// 判斷之後加載
}
return instance;
}
以上可以保證線程安全,但每次都需要獲取鎖,承受同步帶來的性.能開銷.所以我們不需要每次都獲取鎖,只是在創建實例的時候需要而已,下面的代碼再加一重檢測,每次調用函數時再第二次檢測時才考慮取鎖,避免了同步開銷。
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {// 1
if (instance == null) {// 2
instance = new Singleton();// 3
}
}
}
return instance;
}
}
上面的代碼還是有些小問題,就是instance = new Singleton()可能出現重排序.如下,我們默認的是
inst = allocat(); // 分配內存
constructor(inst); // 先執行構造函數
instance = inst; // 後賦值
但虛擬機可能導致
inst = allocat(); // 分配內存
instance = inst; // 先賦值
constructor(inst); // 後執行構造函數
若是第二種,其他線程訪問時,instance不爲null但構造函數沒有完成而導致程序崩潰.所以上面的程序需要加上避免重排的發生,這時就引入了 volatile 關鍵字。
private volatile static Singleton instance = null;
參考:
http://jiangzhengjun.iteye.com/blog/652440
http://www.jianshu.com/p/5b2f063d9f68