雙檢索單列:
double check lock
//綜合了懶漢和惡漢的優點,解決了缺點
//及延遲了對象的實例化,有保證線程安全
關鍵點:
(1).私有構造函數;
(2).通過一個靜態方法或者枚舉返回單例類對象;
(3).確保單例類的對象有且只有一個,尤其是多線程環境下;
(4).確保単例類對象在反序列化時不會重新構建對象.
三.實現方式
1.餓漢式
/**
* 惡漢
* 線程安全 消耗資源
*/
public class SingleOne {
private static final SingleOne singleOne = new SingleOne();
private SingleOne() {
}
public static SingleOne getSingleOne() {
return singleOne;
}
}
2.懶漢式
/**
* 懶漢式
* 多線程不安全,消耗資源少
*/
public class SingleTwo {
private static SingleTwo singleTwo;
private SingleTwo() {
}
public static synchronized SingleTwo getInstance() {
if (singleTwo == null) {
singleTwo = new SingleTwo();
}
return singleTwo;
}
}
3.Double Check Lock(DCL)
public class SingleThree {
private static SingleThree singleThree = null;
private SingleThree() {
}
public static SingleThree getInstance() {
if (singleThree == null) {
synchronized (SingleThree.class) {
if (singleThree == null) {
singleThree = new SingleThree();
}
}
}
return singleThree;
}
}
進行了兩次非空判斷:
(1).第一次避免不必要的同步;
(2).第二次是在非空的情況下創建實例.
上面寫法是有缺陷的,應該將sInstance定義為private volatile static Singleton sInstance = null;具體分析如下:
假設A線程執行到是Instance= newSingleton();時,實際上它不是一個原子操作,它會被編譯成多條匯編指令,大致做了3件事:
(1).給Singleton實例分配內存;
(2).調用Singleton的構造,初始化成員字段;
(3).將sInstance指向分配的內存空間(sInstance就不是null了).
但是由于Java編譯器允許處理器亂序執行,以及JDK1.5之前JMM(Java Memory Model,java內存模型)中Cache,寄存器到主內存回寫順序的規定,上面(2)(3)執行順序是無法保證的,有可能是1-3-2,然后被切換到B線程,sInstance已經非空了,B線程直接取走使用就會出錯,這就是DCL失效問題,而且很難跟蹤.(雙重檢查鎖定失效)
所以JDK1.5及以后要將sInstance改為private volatile static Singleton sInstance = null,這樣可以保證sInstance每次都從主內存中讀取,當然volatile會影響一點性能.
DCL優點:資源利用率高;
缺點:第一次加載反應慢,在高并發環境下有一定缺陷,不過概率很小.