設計模式之單例

設計模式之單例

定義

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。html

簡單來講,也就是須要保證一個類在整個應用程序的生命週期內,只能存在一個實例(沒有也行)。爲了達成這個目標,應該要作到如下幾點:java

  1. 私有的構造器,若是是構造器被聲明爲public的,則沒法控制其實例化;
  2. 一個獲取實例的公開方法,也就是定義中所說的『提供一個訪問它的全局訪問點』;
  3. 一個用來保持此惟一實例的靜態變量。

實現方法

首先,咱們按照前面說的三點,寫了如下代碼:設計模式

public class Singleton {

    private Singleton instance;

    private Singleton(){}

    public Singleton GetInstance(){
        return instance;
    }
}

這樣,顯然是有問題的。安全

一是獲取實例的公開方法getInstance,只是生命成public的,外部在沒有Singleton的實例的狀況下仍是不能調用,就成了一個悖論了。因此須要把GetInstance方法聲明爲靜態的。一樣,因爲須要被靜態方法調用,同時還要用來保持惟一的實例,instance也須要聲明爲靜態的。多線程

二就是還缺乏了對instance變量的初始化,即對構造器的調用。咱們既能夠再聲明instance是就進行初始化,也能夠在靜態代碼段中對它進行初始化。併發

因而乎,就有了下面兩個版本的實現:oracle

方法一:餓漢一

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton GetInstance(){
        return instance;
    }
}

方法二:餓漢二

public class Singleton {

    private static Singleton instance;
    
    static {
        instance = new Singleton();
    }

    private Singleton(){}

    public static Singleton GetInstance(){
        return instance;
    }
}

因爲這兩種方法沒有實質上的區別,都是在類被加載的時候就進行了實例的初始化(因此被稱爲餓漢式)。這也是餓漢式的下的兩個特色:性能

  1. 線程安全,由於在類加載時已經完成來實例化;
  2. 性能低,實例化後的對象在應用的聲明週期中未必就會被使用,因此可能會產生計算的浪費。

方法三:懶漢

因爲餓漢式在類加載時就完成了實例化,致使了可能存在性能浪費,因此咱們就考慮看看能不能在類被使用時才被實例化呢。若是據說過『懶加載』這個的詞話,應該就會以爲這很easy了,在以前的基礎上,很輕鬆就寫出了下面代碼(懶漢式的單例)。優化

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}

    public static LazySingleton GetInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

乍一看,已是能夠了,至少在單線程下已經沒問題了。可是在多線程的場景下呢,很容易就會產生A、B兩個線程同時調用GetInstance方法,A線程先判斷instance爲null,準備進行實例化,但在實例化以前B線程也進行了instance爲null的判斷,最終結果是兩個線程分別調用了一次私有構造器進行實例化,第二次實例化的結果會將第一次的覆蓋掉。線程

因此懶漢式有如下特色:

  1. 懶加載實現,第一次調用時實例化,不調用則不實例化,故計算效率高;
  2. 線程不安全,上面已經詳細描述來是如何產生線程衝突的。

方法四:懶漢(線程安全)

爲了解決上面方法的缺陷,也就是所謂的線程不安全,咱們找到了synchronized這個關鍵字,也只加了這個關鍵字,獲得下面的代碼。

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}

    public static synchronized LazySingleton GetInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

與前一種方法相比,在實現上只是在getInstance方法上增長了synchronized關鍵字,使得GetInstance方法同步,同一時間只能有一個線程執行這個方法,那咱們以前說的線程不安全的問題顯然就不在了,這樣是否是就完美來呢?世界沒那麼美好,咱們又引入了新的問題。

因爲是在整個GetInstance方法上加鎖(同步),可是由於實際上只須要進行一次實例化(也只容許進行一次),因此絕大多數場景下是不須要同步的,因此在併發場景下會致使效率下降,至關於多車道在這裏併成單車道了。

方法五:懶漢(雙重檢查鎖定)

既然還有問題,那咱們就來繼續進行優化。上面的方法由於把整個GetInstance方法設置爲synchronized,因此致使多線程在這裏受阻,那咱們把同步的範圍縮小一點兒,看看狀況會不會好一些。

public class LazySingleton {
    private static volatile LazySingleton instance;
    private LazySingleton() {}

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

優化以後,把鎖定範圍進行了收縮,只在須要進行初始化實例時才進行同步,以後就再也不進行同步。

這樣咱們就獲得了一種效率較高,而且線程安全的單例模式的構造方法。

PS. 若是有仔細看代碼,您或許會發現咱們在聲明instance變量的時候,用了一個volatile關鍵字,若是須要一些解釋的話,能夠參考Java中的volatile在使用雙層檢查實現單例模式的解讀,後面我也可能來單獨說一下這個。

第六種:靜態內部類

還有一種使用靜態內部類來實現的單例,也被各類推薦。由於內部靜態類是要在有引用了之後纔會裝載到內存的,這樣就一樣實現了懶加載;同時,靜態內部類的靜態變量的初始化,也是在被加載時進行的初始化,自然的完成來對進程安全的控制。

public class InnerStaticClassSingleton {
    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static InnerStaticClassSingleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

To Be Continued

相關文章
相關標籤/搜索