前言
單例顯而易見也就是單獨、唯一的意思,即一個類只有一個實例,比較簡單、常用的一種設計模式
舉個栗子
皇帝:
中國有許多的皇帝,一般同一時間段只會有一個皇帝(個別的兩個皇帝的就不說了啊),那我們就能認爲皇帝就是一個單例模式,在這個場景中,有皇帝、大臣,大臣每天都需要去上朝參見皇帝,今天參見的皇帝和昨天前天參見的皇帝都是一樣的。單例模式,絕對的單例模式
使用場景
- 該類佔用較多的資源,如線程、IO、網絡請求等
- 該類的數據是全局的、共享的
- 該類的實例生命週期應該是全局的,在application整個生命週期中都會用到的
實現單例模式的幾種方法
實現單例模式需要關注幾個關鍵點
- 構造函數私有化,即權限設置爲private
- 通過一個構造方法或者枚舉返回單例類對象
- 確保單例類的對象只有一個,尤其是多線程的情況下
- 確保單例對象在反序列表不會重複創建對象
構造函數的私有化,確保了使用者不能夠通過new來創建單例類的實例對象,該類暴露給外界一個獲取唯一實例的方法,同樣還需要保證線程安全,不能夠在多線程的時候出現單例類存在多個對象的情況,即單例類在生命週期中只能存在一個實例供外界使用。
實現單例的方式
餓漢式
private static Singleton singleton= new Singleton();
private Singleton(){
}
private static Singleton getInstance(){
return singleton
}
餓漢式由於類加載時就創建好了對象,不存在線程安全和效率的問題,缺點是過早的創建了對象,而且不能夠傳遞參數。
肯定也沒人這麼寫,假如有推出去斬了
懶漢式
//定義一個實例
private static Singleton singleton = null;
/**
* 構造函數私有化
*/
private Singleton() {
}
/**
* 提供一個靜態函數,獲取實例,synchronized進行同步
* @return
*/
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
這種方式簡單,但是每次創建實例對象的時候都需要同步,同步是需要消耗資源的,效率低,不建議使用
Double Check Lock實現單例——改進的懶漢式
目前使用最廣泛的單例,線程安全並且效率不低,可以考慮使用
//定義一個實例
private volatile static Singleton singleton = null;
/**
* 構造函數私有化
*/
private Singleton() {
}
/**
* 提供一個靜態函數,獲取實例,synchronized進行同步
*
* @return
*/
public static Singleton getInstance() {
if (singleton == null) {//第一次檢查
synchronized (Singleton.class) {//lock
if (singleton == null) {//第二次檢查
singleton = new Singleton();
}
}
}
return singleton;
}
注意到使用了volatile關鍵字修飾單例對象,這樣可以保證singleton對象每次都從主內存中讀取,從而避免了由與java內存模型帶來不必要的麻煩。
synchronize沒有加在方法上,而是加在了方法體上,調用該方法的時候先檢查實例對象爲空,爲空的時候加同步(防止多線程出現多個實例的情況),並且再次判斷實例是否爲空,爲空就創建。這種方式只會在第一次實例爲空的情況下同步,克服了懶漢式模式下每次都需要同步的情況,避免了不必要的開支
靜態內部類實現單例
比較好的單例,線程安全,保證唯一,延遲實例化。建議選用
/**
* 構造函數私有化
*/
private Singleton() {
}
/**
* 私有內部靜態類,利用了加載外部類的時候內部類不會立即被加載的特性
*/
private static class SingletonHolder{
private static Singleton singleton = new Singleton();
}
/**
* 提供一個靜態函數,獲取實例
*
* @return
*/
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
使用了java內部類的加載機制,只有內部類的靜態成員被調用的時候纔會加載靜態內部類,所以會延遲加載
枚舉單例
public enum Singleton {
SINGLETON;
private Singleton(){
}
}
枚舉和java中其他類一樣,不僅可以有字段,也可以有自己的方法,如果我們需要一個對象來實現一個簡單的事件(顯示Toast),就可以使用枚舉,枚舉默認就是單一實例的並且創建默認是線程安全的
使用容器使用單例
private static Map<String, Object> instanceMap = new HashMap<String, Object>();
private Singleton() {
}
public static void putInstance(String key, Object instance) {
if (!instanceMap.containsKey(key)) {
instanceMap.put(key, instance);
}
}
public static Object getInstance(String key){
return instanceMap.get(key);
}
這種單例模式在Android源碼中有用到,在單例比較多的時候,通過容器來管理和獲取單例實例是一個不錯的辦法,通常在程序初始化的時候會將多個單例實例存到容器中,這樣可以管理這些實例,下次使用直接取。
總結
單例模式是面向對象設計模式中應用最簡單、最廣泛的一種設計模式
- 單例模式的優點
- 提高了系統的性能
- 減少了系統內存的開銷
- 單例模式避免對資源的多重佔用保證系統整體的協調
- 單例對象可以設置全局的通訊站點, 優化和共享資源訪問
- 單例迷失的缺點
- 單例模式一般不存在接口,也不是抽象的,不易拓展,如要拓展需要改變類的源代碼,這和開閉原則相違背
- 單例模式的對象很容易產生一些問題,如:如爲了節省資源將數據庫連接池對象設計爲的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認爲是垃圾而被回收,這將導致對象狀態的丟失。
- 單例類的職責過重,某種程度違背了單一職責原則