Talk is cheap, show me the code.
常用多種單列模式,如下:
public class SingleExample {
? ? public static SingleExample singleInstance;
? ? private SingleExample(){}
? ? public SingleExample getInstance(){
? ? ? ? if(singleInstance==null)singleInstance=new SingleExample();
? ? ? ? return singleInstance;
? ? }
}
僅有一個線程引用該形式,是沒有問題的,如果多線程使用時,無法保證僅創建一個實例;
如下進行一次優化
public synchronized SingleExample getInstance(){
? ? if(singleInstance==null)singleInstance=new SingleExample();
? ? return singleInstance;
}
使用線程鎖,保證單例唯一性;但是如此就會每次調用getInstance需要進行同步,浪費不需要的資源;
public synchronized SingleExample getInstance(){
? ? if(singleInstance==null){
? ? ? ? synchronized (SingleExample.class){
? ? ? ? ? ? if(singleInstance==null){
? ? ? ? ? ? ? ? singleInstance=new SingleExample();
? ? ? ? ? ? }
}
}
? ? return singleInstance;
}
雙重校驗(DCL)會在某些情況失效,原因如下:
? singleInstance=new SingleExample();這個執行步驟如下:
1)給singleInstance分配內存;
2)調用SingleExample()構造函數,初始化成員變量;
3)將singleInstance對象指向分配的內存空間;(此時singleInstance就不是null了)
????????但是,由于java編譯器允許處理器亂序執行,以及JDK1.5前JMM(Java內存模型)中Cache,寄存器到主內存回寫順序的規定,上面的第二和第三的順序是無法保證的,也就導致上面步驟的執行順序可能是1-2-3,也可能是1-3-2。
? ? ? ? 這樣就導致在A線程執行完1-3后,B線程判斷singleInstance不為null,直接使用singleInstance,導致程序異常。
? ? ? ? 在JDK1.5后,SUN官方優化了volatile關鍵字去解決該問題,保證singleInstance不為null時,已經進行了初始化;
? ???????public volatile?static SingleExample singleInstance;
當然使用volatile關鍵字會對內存有寫影響,但是可以保證程序執行的正確性。
但是如上的方式是比較繁瑣,對性能也是有些影響的,我們可以通過Java中類的靜態成員變量初始化機制(當且僅在類被加載時,進行初始化)來實現單例模式,如下;
public class SingleExample {
? ? private SingleExample(){}
? ? public SingleExample getInstance(){
? ? ? ? return SingletonHolder.singleInstance;
? ? }
? ? private static class? SingletonHolder{
? ? ? ? //靜態成員變量僅在類初次加載時,進行初始化,這樣保證了對象唯一性
? ? ? ? private static final SingleExample singleInstance=new SingleExample();
? ? }
}