1. 程式人生 > 實用技巧 >單例模式詳解(二)

單例模式詳解(二)

  不知道哪位大神,提供了一種單例的寫法,思考角度也很絕妙,從類初始化的角度考慮。這也是一種經典的單例實現方式,內部類單例實現。廢話不多說,上程式碼:

  餓漢式單例(四)

  

/**
 * 內部類單例
 */
public class InnerClassSingleton {

    private InnerClassSingleton(){}


    public static InnerClassSingleton getInstance(){
        //在返回之前,一定要先載入內部類
        return Innerclass.instance;
    }

    
//利用了java的語法特點,預設不載入內部類 private static class Innerclass{ private static final InnerClassSingleton instance = new InnerClassSingleton(); } }

  內部類的單例實現方式兼顧了餓漢式單例的記憶體浪費問題和synchronized加鎖帶來的效能損耗問題,並且內部類的實現方式只有在呼叫getInstance()方法的結果返回之前載入內部類,完成例項的初始化,巧妙的避免了執行緒安全問題,在不考慮序列化破壞單例和暴力反射破壞單例模式的情況下,這種實現方式堪稱完美。接下來我們來看看暴力反射是如何破壞單例的:

  暴力反射破壞單例模式:

public static void main(String[] args) {
       try{
           Class<?> clazz = InnerClassSingleton.class;
           Constructor c = clazz.getDeclaredConstructor(null);
           c.setAccessible(true);
           //反射建立兩個例項
           Object o1 = c.newInstance();
           Object o2 
= c.newInstance(); System.out.println(o1==o2); //false }catch (Exception e){ e.printStackTrace(); } }

  最終,在程式碼的最後一行一定是輸出false,在記憶體中存在兩個不同的例項,這樣一來就違背了單例模式的初衷,有沒有辦法解決它呢?當然有,我們可以這優化內部類實現的單例模式:

/**
 * 內部類單例
 */
public class InnerClassSingleton {

    private InnerClassSingleton(){}
    
    public static InnerClassSingleton getInstance(){
        //加上判斷,如果建立多個例項就丟擲異常
        if(Innerclass.instance!=null){
            throw new RuntimeException("禁止建立多個例項!");
        }
        //在返回之前,一定要先載入內部類
        return Innerclass.instance;
    }

    //利用了java的語法特點,預設不載入內部類
    private static class Innerclass{
        private static final InnerClassSingleton instance = new InnerClassSingleton();
    }
}

  這樣似乎就大功告成啦!

  當然除了以上幾種常見的單例實現,還有一些單例實現方式:

  列舉式單例:

  

/**
 * 列舉單例
 */
public enum  EnumSingleton {
    INSTANCE;
    private Object object;

    public Object getObject(){
        return object;
    }

    public void setObject(Object object){
        this.object = object;
    }

    public static Object getInstance(){
        return INSTANCE;
    }
}

  列舉式單例是《Effective Java》書中比較推薦的一種單例實現方式,列舉的單例實現不僅是執行緒安全的,而且也能保證單例模式不會被破壞,可以說是一種既簡單又安全的實現方式。

  容器式單例:

  看到這種實現的名字可能有些小夥伴會感到陌生,我們大名鼎鼎的spring框架的單例就是基於此方式進行實現的。容器式單例的實現方式適用於需要大量建立單例物件的場景,便於管理。我們直接來看看spring的原始碼實現:

  

private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256);

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //從快取singletonObjects(實際上是一個map)中獲取bean例項
        Object singletonObject = this.singletonObjects.get(beanName);
    //如果為null,對快取singletonObjects加鎖,然後再從快取中獲取bean,如果繼續為null,就建立一個bean。
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            Map var4 = this.singletonObjects;
            //雙重判斷加鎖
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
            //通過 singletonFactory.getObject() 返回具體beanName對應的ObjectFactory來建立bean。
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
        }  
    } 
}

  單例模式看似簡單,其實裡面還是還是有一些很深的學問值得研究的,比如我們的列舉式單例式怎麼樣防止惡意破壞的,這一點感興趣的小夥伴可以自行研究一下,我這裡就不貼出來了。單例模式的優點也有很多,比如:

  1、單例模式在記憶體中只有一個例項,可以減少記憶體開銷;

  2、單例模式只提供一個全域性的訪問點,可以達到資源共享的目的。

  當然缺點也有,比如:

  1、單例模式沒有介面,如果要擴充套件必須修改原來的程式碼,違背開閉原則;

  2、單例模式的功能程式碼通常寫在一個類中,如果設計不合理,很容易違背單一職責原則。

  至此,單例模式的介紹就到此啦~