1. 程式人生 > >Java之單例模式的各種實現

Java之單例模式的各種實現

最近連續在各種群裡、部落格裡看到單例模式的討論。根據我的理解總結一下:
先直接說結論:最優雅最簡潔最穩的方法是使用列舉實現單例模式。

餓漢式

//無懶載入
//在類載入時初始化唯一的例項物件,由jvm在多執行緒環境時保證執行緒安全
//增加了初始化的時間和記憶體開銷
public class SingleDog {
    private static final SingleDog instance = new SingleDog();

    private SingleDog(){}

    public SingleDog getInstance(){
        return instance;
    }
}

懶漢式

//懶載入
//因為例項化在getInstance裡執行,所以每次訪問instance,都需要獲取同步鎖,比較笨重;
public class SingleDog {
private static SingleDog instance = null;

private SingleDog(){}

synchronized public static SingleDog getInstance(){
    //判斷是否需要對Instance進行初始化
    if(instance == null){
        instance = new SingleDog();
    }
    return
instance; }

懶漢式多執行緒環境下優化 之 雙重檢查鎖定

//懶載入(雙重檢查鎖定)
//多執行緒訪問時,只在可能需要對Instance進行初始化時獲取鎖,大多數時候直接讀取Instance即可
public class SingleDog {
    //需用volatile關鍵字保證對這個變數所做的操作是所有執行緒可見的
    private volatile static SingleDog instance = null;

    private SingleDog(){}

    public static SingleDog getInstance(){
        //判斷是否需要對Instance進行初始化
if(instance == null){ synchronized(SingleDog.class){ //對於已經獲取到鎖的其他執行緒,再次判斷Instance是否需要進行初始化 if(instance == null){ instance = new SingleDog(); } } } return instance; } //雙重檢查鎖定也不能避免使用重度鎖synchronized,在獲取和釋放鎖的過程中會有效能損耗;同時需要兩個if判斷來確保只有一個例項,程式邏輯比較複雜

懶漢式多執行緒環境下優化 之 靜態內部類實現


//與第一個餓漢式相比,使用一個私有靜態內部類來代替SingleDog類,持有instance例項,達到了懶載入的目的。
//擺脫了重度鎖synchronized
//那麼多執行緒環境下一個類是否會被初始化多次呢?
//jvm在類載入時會確保執行緒的安全,如果多個執行緒去初始化一個類,只會有第一個執行緒被執行,其他執行緒都會被阻塞而且不會再次進入到類的初始化中去。同一個類載入器下,一個類只會被初始化一次
public class SingleDog {
    private SingleDog() {}

    private static class SingleHolder{
        private static SingleDog instance = new SingleDog();
    }

    public static SingleDog getInstance(){
        return SingleHolder.instance;
    }
}

有什麼辦法破壞上述單例模式

反射

雖然建構函式已經被限定為private了,但是隻要有建構函式存在,就可以通過Java反射機制獲取到它,還能強制性的setAccessble,將其設定為可訪問,從而利用建構函式再生成一個新的例項,破壞單例模式。部分程式碼如下:

        SingleDog s1 = SingleDog.getInstance();
        Class<SingleDog> cls = (Class<SingleDog>) s1.getClass();
        Constructor<SingleDog> cons = cls.getDeclaredConstructor(new Class[] {});
        cons .setAccessible(true);
        SingleDog s2 = cons.newInstance(new Object[] {});
        System.out.println(s1 == s2);//false

反序列化

當將Java物件序列化後,再反序列時,readObject方法會自動建立一個新的例項。天然破壞了被序列化物件的單例模式。
不過這個問題很好解決,只需要在類中新增一個方法就行:

public class SingleDog {
    private SingleDog() {}

    private static class SingleHolder{
        private static SingleDog instance = new SingleDog();
    }

    public static SingleDog getInstance(){
        return SingleHolder.instance;
    }
    //劃重點!
    public Object readResolve() {
        return instance;
    }
}

列舉實現單例模式

//列舉實現單例模式
//防反射、防反序列化、執行緒安全(例項在類初始化期間就已經建立)
//列舉類實際上是繼承於java.lang.Enum的一個抽象類,所以是無法用反射獲取到其構造方法的
enum SingleDog {
    INSTANCE;
    public static SingleDog getInstance() {
        return INSTANCE;      
    } 
}