1. 程式人生 > >設計模式(01) 單例模式(建立類模式)(上,兩種推薦的實現方法)

設計模式(01) 單例模式(建立類模式)(上,兩種推薦的實現方法)

From Now On,Let us begin Design Patterns。

單例模式

定義

  • 確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。 Ensure a classhas only one instance, and provide a global point of access to it.

單例模式有如下幾個特點:

1. 它只有一個例項

2. 它必須要自行例項化

3. 它必須自行想整個系統提供訪問點

單例模式的優點:

  • 記憶體僅有一個例項,減少記憶體開支;特別是一個物件需要頻繁地建立、銷燬時,而且建立或銷燬時效能又無法優化,單例模式的優勢就非常明顯。

  • 當一個物件的產生需要比較多的資源時,如讀取配置、產生其他依賴物件時,則可以通過在應用啟動時直接產生一個單例物件,然後用永駐留記憶體的方式來解決(在Java EE中注意JVM垃圾回收機制)。

  • 單例模式可以在系統設定全域性的訪問點,優化和共享資源訪問,例如可以設計一個單例類,負責所有資料表的對映處理。

  • 在單例模式中,活動的單例只有一個例項,對單例類的所有例項化得到的都是相同的一個例項。這樣就 防止其它物件對自己的例項化,確保所有的物件都訪問一個例項

  • 由於在系統記憶體中只存在一個物件,因此可以 節約系統資源,當 需要頻繁建立和銷燬的物件時單例模式無疑可以提高系統的效能

  • 避免對共享資源的多重佔用

單例模式的缺點:

  • 單例模式一般沒有介面,擴充套件很困難,若要擴充套件,除了修改程式碼基本上沒有別的途徑。單例模式為什麼不能增加介面呢?因為介面對單例模式是沒有任何意義的,它要求“自行例項化”,並且提供單一例項。介面、抽象類是不可能被例項化的。當然特殊情況下,單例模式可以實現介面、被繼承等,需要在系統開中根據環境判斷。

  • 單例模式對測試不利。在並行開發環境中,如果單例沒有完成,是不能進行測試的。

  • 單例模式與單一職責原則有衝突。一個類應該只實現一個邏輯,而不關心它是否是單例的,是不是要取決於環境,單例模式把“要單例”和業務邏輯融合在一個類中。單例類的職責過重,在一定程度上違背了“單一職責原則”。

  • 由於單利模式中沒有抽象層,因此單例類的擴充套件有很大的困難

  • 不適用於變化的物件,如果同一型別的物件總是要在不同的用例場景發生變化,單例就會引起資料的錯誤,不能儲存彼此的狀態。

  • 濫用單例將帶來一些負面問題,如為了節省資源將資料庫連線池物件設計為的單例類,可能會導致共享連線池物件的程式過多而出現連線池溢位;如果例項化的物件長時間不被利用,系統會認為是垃圾而被回收,這將導致物件狀態的丟失。

單例模式的使用場景:

在一個系統中,要求一個類有且僅有一個物件,如果出現多個物件就會出現“不良反應”,可以採用單例模式,具體場景如下:

1. 要求生成唯一序列號的環境;

2. 在整個專案中需要一個共享訪問點或共享資料,如計數器;

3. 建立一個物件需要消耗資源過多,如要訪問IO和資料庫等資源;

4. 需要定義大量的靜態常量和靜態方法(如工具類)的環境,可以採用單例模式。

單例模式的實現方法(兩種推薦的方法):

1.餓漢式單例:

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){}
    //獲取單例
    public static Singleton getInstance(){
        return singleton;
    }
}

單例例項singleton在類Singleton裝載時就構建,急切初始化。只要類已經被載入進入JVM,我們就可以直接通過getInstance方法獲取到這個單例類。(預先載入法)

優點:

  • 執行緒安全
  • 在類載入的同時已經建立好一個靜態物件,呼叫時反應速度快

缺點:

  • 資源效率不高,可能getInstance()永遠不會執行到,但執行該類的其他靜態方法或者載入了該類(class.forName),那麼這個例項仍然初始化

2.靜態內部類獲取單例:

public class Singleton {
    private static class SingletonTool{
        static Singleton singleton = new Singleton();
    } 
    private Singleton(){}
    //獲取單例
    public static Singleton getInstance(){
        return SingletonTool.singleton;
    }
}

這裡如果我們想知道為什麼可以實現單例,必須深入的理解static關鍵字:
在一個.java檔案中,我們會將.java檔案編譯成.class檔案。在這個.class檔案首次載入進入記憶體的時候,
第一步:虛擬機器會將這個類裡面所有的static變數的分配一個指標(應該就是初始化),但是還沒有賦值。
假如類中有一個:

static int a = 3;

其實等價於:

static int a ;
static{
a = 3;
}

如果我們在:a = 3;之前輸出a;會發現a = 0;
第二步:虛擬機器會將所有的static方法從上到下依次執行。
舉例說明問題:
這裡寫圖片描述
jvm會對static內部類進行優化,到你使用的時候才會載入內部類進來。

優點:

  • 執行緒安全
  • 資源利用率高,不執行getInstance()不被例項,可以執行該類其他靜態方法,這個時候單例類沒有被例項化。

缺點:

  • 第一次使用單例類物件的時候,物件還沒有被例項化,載入時反應不夠快

一般採用餓漢式,若對資源十分在意可以採用靜態內部類,不建議採用懶漢式及雙重檢測 (我們下一篇文章詳細講解不會用到的懶漢式和雙重檢測)