如何讓孩子愛上設計模式 —— 2.單例模式(Singleton Pattern)

標簽: 設計模式初涉


描述性文字

23種不同的設計模式,分為三大類:

23種設計模式思維導圖
23種設計模式思維導圖

本節描述的是最簡單爛大街的單例模式。


1.應用場景:

當需要保證類在內存中的對象唯一性,可以使用單例模式,
不想創建多個實例浪費資源,或者避免多個實例由于多次調用
而出現錯誤。一般寫工具類,線程池,緩存,數據庫等可以用到。


2.設計思想:(保證對象的唯一性的三個步驟)

想法 實現
不允許其他程序用new對象 私有化該類的構造函數
在該類中創建對象 通過new在本類中創建一個本類對象
對外提供一個可以讓其他程序獲取該對象的方法 定義一個公有的方法,將在該類中所創建的對象返回

3.各種各樣的單例寫法


1.餓漢模式

public class Singleton () {
    private static Singleton instance = new Singleton()
    private Singleton(){ }
    public static Singleton getInstance() {
        return instance;
    }
}

獲取單例對象:Singleton instace = Singleton.getInstance();
優點:類加載的時候就完成實例化,避免線程同步問題
缺點:由于在類加載的時候就進行了實例化,所以沒有達到Lazy Loading(懶加載)
的效果,即使我們沒用到這個實例,但是他還是會加載,從而造成內存浪費(可以忽略,
最常用的一種)。


2.懶漢模式(線程不安全,不可用)

public class Singleton {
    private static Singleton instance = null;
    private Singleton() { }
    private static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

盡管達到了懶加載,但是卻存在線程安全問題,比如有兩個線程,剛好都
執行完if(instance == null),接著準備執行instance = new Singleton()
語句,這樣的結果會導致我們實例化了兩個Singleton對象,這就是懶漢單例
模式可能會引發的線程安全問題,解決這個方法,我們可以對getInstance方法加鎖。


3.懶漢模式(線程安全,但效率低,不推薦使用)

public class Singleton { 
    private Singleton instance = null;
    private Singleton() { }
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

盡管保證了線程安全,但是每個線程在想要獲得實例時,執行getInstance()
方法都需要進行同步,而實例化代碼只需執行一次就夠了,后面獲取該實例,
直接return即可,方法進行同步效率太低,需要改進。還有一種寫法是:
synchronized (Singleton.class) { instance = new Singleton(); }
這樣一樣是線程不安全的,如果你想使用懶漢模式的話,推薦使用下面的:
DCL單例(雙重檢查鎖定)。


4.懶漢模式雙重校驗鎖

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() { }
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

代碼中進行了兩次if檢查,這樣就可以保證線程安全,初始化一次后,
后面再次訪問時,if檢查,直接return 實例化對象。volatile是1.5后
引入的,volatile關鍵字會屏蔽Java虛擬機所做的一些代碼優化,會導
致系統運行效率降低,而更好的寫法是使用靜態內部類來實現單例!


5.靜態內部類實現單例模式

public class Singleton {
    private Singleton() { }
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

和餓漢式類似,兩者都是通過類裝載機制來保證初始化實例
的時候只有一個線程,從而避免線程安全問題,餓漢式的
Singleton類被加載,就會實例化,而靜態內部類這種,
當Singleton類被加載時,不會立即實例化,調用getInstance方法,
才會裝載SingletonHolder類,從而完成Singleton的實例化。


6.枚舉實現單例模式

public enum SingletonEnum {
    INSTANCE;
    private Singleton instance;
    SingletonEnum() { 
        instance = new Singleton()
    }
    public Singleton getInstance() {
        return instance;
    }
}

訪問方式:SingletonEnum.INSTANCE.method();
INSTANCE即為SingletonEnum類型的引用,得到它就可以調用
枚舉中的方法。既避免了線程安全問題,還能防止反序列化
重新創建新的對象,但是失去了類的一些特性,沒有延時加載,
推薦使用。


7.使用容器實現單例模式

public class SingletonManager {
    private static Map<String,Object> objMap = new HashMap<String,Object>();
    private Singleton() { }
    public static void registerService(String key,Object instance) {
        if(!objMap.containsKey(key)) {
            objMap.put(key,instance);
        }
    }
    public static Object getService(String key) {
        return objMap.get(key);
    }
}

將多種單例類型注入到一個統一的管理類中,在使用時根據key獲取對象
對應類型的對象。這種方式使得我們可以管理多種類型的單例,并且在使
用時可以通過統一的接口進行獲取操作,降低了用戶的使用成本,也對用
戶隱藏了具體實現,降低了耦合度。


優缺點

優點:保持類對象唯一性,對于頻繁創建和銷毀的對象可以提高性能。
缺點:擴展困難,單例的方法無法生成子類對象,要擴展的話基本要重寫這個類。

注意事項:

在Android中如果單例對象持有Context,那么很容易引發內存泄露。
此時需要注意傳遞給單例對象的Context最好是Application Context!!!


本文代碼

https://github.com/coder-pig/DesignPatternsExample/tree/master/1.Singleton%20Pattern

修改日志

  • 2017.1.24 增加設計模式思維導圖,添加本文代碼鏈接
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容