單例模式的終極實現方案

單例模式(Singleton)是一種使用率很是高的設計模式,其主要目的在於保證某一類在運行期間僅被建立一個實例,併爲該實例提供了一個全局訪問方法,一般命名爲getInstance()方法。單例模式的本質簡言之便是:java

控制實例數目設計模式

以Java爲例,單例模式一般可分爲餓漢式懶漢式兩種常規實現方式緩存

餓漢式單例實現

餓漢式顧名思義,就是對類實例(食物?)的需求很是強烈,所以,在裝載該單例類的時候就會建立類實例。以下安全

public class Singleton {
    /**
     * 裝載時即建立類實例,並保存在類變量instance中
     * 加上static關鍵詞使得該變量能在getInstance()靜態方法中使用
     */
    private static Singleton instance = new Singleton();
 
    /**
     * 私有化構造方法,使外部沒法經過構造方法構造除instance外的類實例
     * 從而達到單例模式控制類實例數目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實例的全局訪問方法
     * 加上static關鍵詞使得外部能夠經過類名直接調用該方法獲取類實例
     * @return 單例類實例
     */
    public static Singleton getInstance() {
        //  因爲類實例在類裝載時已被建立並保存在instance中,所以可直接返回
        return instance;
    }
}

事實上,在Android開發中,Android Studio提供了一個直接建立單例類的功能(File->new->Singleton),該功能自動生成的單例類正是採用了餓漢式的實現方式多線程

懶漢式單例實現

說到懶,咱們天然而然會想到拖延症這一惡習,這一點和懶漢式的單例實現方式類似,這一實現方式會一直等到真正須要使用對象實例的時候再去建立該實例。以下併發

public class Singleton {
    /**
     * 裝載時不建立類實例,但須要利用一個類變量去保存後續建立的類實例
     * 添加static關鍵詞使得該變量能在getInstance()靜態方法中使用
     */
    private static Singleton instance = null;
 
    /**
     * 私有化構造方法,使外部沒法經過構造方法構造除instance外的類實例
     * 從而達到單例模式控制類實例數目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實例的全局訪問方法
     * 添加static關鍵詞使得外部能夠經過類名直接調用該方法獲取類實例
     * @return 單例類實例
     */
    public static Singleton getInstance() {
        //  若是instance未被初始化,則初始化該類實例
        if (instance == null) {
            instance = new Singleton();
        }
 
        return instance;
    }
}

事實上,雖然咱們前面拿拖延症來與懶漢式作類比,但懶漢式的拖延倒是實際開發中的一種較爲常見的節省資源的方式,即延遲加載思想。這一思想的核心在於直到須要使用某些資源或數據時再去加載該資源或獲取該數據,這樣能夠儘量地節省使用前的內存空間線程

線程安全的懶漢式單例實現

不難分析出,當外部多個線程同時想要獲取單例類實例時,上述懶漢式實現方式便很容易致使併發問題。一般有以下幾種改進方式設計

添加synchronized關鍵詞

....
public static synchronized Singleton getInstance() {
....

這種改進方式是最簡單的,但因爲外部每次調用getInstance()方法時均需進行判斷,所以該方式也是效率較低的code

利用雙重檢查加鎖機制

雙重檢查加鎖機制分爲以下兩重檢查對象

  • 在程序每次調用getInstance()方法時先不進行同步,而是在進入該方法後再去檢查類實例是否存在,若不存在則進入接下來的同步代碼塊
  • 進入同步代碼塊後將再次檢查類實例是否存在,若不存在則建立一個新的實例

這樣一來,就只須要在類實例初始化時進行一次同步判斷便可,而非每次調用getInstance()方法時都進行同步判斷,大大節省了時間,具體實現以下

public class Singleton {
    /**
     * 裝載時不建立類實例,但須要利用一個類變量去保存後續建立的類實例
     * 添加volatile關鍵詞使其不會被本地線程緩存,保證線程能正確處理
     * 添加static關鍵詞使得該變量能在getInstance()靜態方法中使用
     */
    private volatile static Singleton instance = null;
 
    /**
     * 私有化構造方法,使外部沒法經過構造方法構造除instance外的類實例
     * 從而達到單例模式控制類實例數目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實例的全局訪問方法
     * 添加static關鍵詞使得外部能夠經過類名直接調用該方法獲取類實例
     * @return 單例類實例
     */
    public static Singleton getInstance() {
        //  第一重檢查:若是instance未被初始化,則進入同步代碼塊
        if (instance == null) {
            //  同步代碼塊,保證線程安全
            synchronized (Singleton.class) {
                //  第二重檢查:若是instance未被初始化,則初始化該類實例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
 
        return instance;
    }
}

利用Java緩存思想實現的單例實現

public class Singleton {
    //  類實例緩存KEY值
    private static final String KEY = "CACHE";
 
    //  類實例緩存容器
    private static Map<String, Singleton> map = new HashMap<>();
 
    /**
     * 私有化構造方法,使外部沒法經過構造方法構造除instance外的類實例
     * 從而達到單例模式控制類實例數目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實例的全局訪問方法
     * 添加static關鍵詞使得外部能夠經過類名直接調用該方法獲取類實例
     * @return 單例類實例
     */
    public static Singleton getInstance() {
        //  嘗試從緩存容器中獲取類實例
        Singleton instance = map.get(KEY);
        //  未能獲取類實例,則初始化該實例,並將其緩存至容器中
        if (instance == null) {
            instance = new Singleton();
            map.put(KEY, instance);
        }
 
        return instance;
    }
}

上述實現方式暫未考慮線程安全問題。事實上,利用緩存來實現的單例模式其最大的優勢在於對單例模式進行擴展。咱們天然而然地能夠想到這麼一種狀況,既然在實際開發中常常須要保證某個類只能被建立一個實例,那麼,會不會出現保證某個類只能被建立兩個或多個實例這種需求呢?對於這項需求,咱們首先能夠想到,上述實現方式中所創建的緩存容器是能夠存儲多個類實例的,利用這一特色,只需考慮一個問題,即外部調用時到底須要爲其返回哪個實例,即可實現「雙例模式」以及「多例模式」(原諒我爲它們取了一些奇怪的名字)了,具體實現以下

public class Singleton {
    //  可建立的最大類實例數,這裏以「雙例模式」爲例
    private static final int MAX = 2;
 
    //  類實例緩存KEY值
    private static final String KEY = "CACHE";
 
    //  當前正在使用的實例序號
    private static int index = 1;
 
    //  類實例緩存容器
    private static Map<String, Singleton> map = new HashMap<>();
 
    /**
     * 私有化構造方法,使外部沒法經過構造方法構造除instance外的類實例
     * 從而達到單例模式控制類實例數目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實例的全局訪問方法
     * 添加static關鍵詞使得外部能夠經過類名直接調用該方法獲取類實例
     * @return 單例類實例
     */
    public static Singleton getInstance() {
        //  嘗試從緩存容器中獲取第index個類實例
        String key = KEY + index;
        Singleton instance = map.get(key);
        //  未能獲取類實例,則初始化該實例,並將其緩存至容器相應index中
        if (instance == null) {
            instance = new Singleton();
            map.put(key, instance);
        }
 
        //  這裏以最基本的順序調用爲例,其餘複雜調度方式不加討論,具體調用方式以下
        //  index++,以在下一次調用中獲取下一個類實例,當達到類實例數上限時,從新獲取第一個類實例
        if ((++index) > MAX) {
            index = 1;
        }
 
        return instance;
    }
}

單例模式的最佳實現

綜合而言,上述實現方式都或多或少地存在諸如線程不安全、沒法作到延遲加載等小缺陷。這裏給出一個能夠稱得上完美的最佳解決方案

Lazy Initialization Holder Class 模式

這一方案的核心在於Java的類級內部類(即便用static關鍵詞修飾的內部類,不然稱之爲對象級內部類)以及多線程缺省同步鎖,先來看看具體實現

public class Singleton {
    /**
     * 類級內部類,用於緩存類實例
     * 該類將在被調用時纔會被裝載,從而實現了延遲加載
     * 同時因爲instance採用靜態初始化的方式,所以JVM能保證其線程安全性
     */
    private static class Instance {
        private static Singleton instance = new Singleton();
    }
 
    /**
     * 私有化構造方法,使外部沒法經過構造方法構造除instance外的類實例
     * 從而達到單例模式控制類實例數目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實例的全局訪問方法
     * 添加static關鍵詞使得外部能夠經過類名直接調用該方法獲取類實例
     * @return 單例類實例
     */
    public static Singleton getInstance() {
        return Instance.instance;
    }
}

在前面提到的餓漢式實現方式中,咱們利用Java的靜態初始化、藉由JVM實現了線程安全,所以這裏一樣採用了這種方式。而另外一方面,爲了不餓漢式實現中沒法進行延遲加載的缺陷,咱們構造了一個類級內部類來緩存類實例,因爲該類只會在經過getInstance()方法去調用時纔會被系統裝載,換言之,只有初次調用getInstance()方法時纔會去初始化類實例,所以也實現了延遲加載這一功能。如此即可使得這一實現方式可以同時具有線程安全、延遲加載以及節省大量同步判斷資源等優點,能夠說是單例模式的最佳實現了

相關文章
相關標籤/搜索