單例模式總結

LiuJian-Android發表於2018-02-28

1 簡介

定義:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

單例模式總結

圖中,Client為客戶端,Singleton是單例類,通過呼叫Singleton.getInstance()來獲取例項物件。

2單例模式的6種寫法

2.1 餓漢模式

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
} 
複製程式碼

這種方式在類載入時就完成了初始化,所以類載入較慢,但獲取物件速度快。這種方式基於類載入機制,避免了多執行緒的同步問題。在類載入的時候就完成了例項化,沒有達到懶載入的效果。如果從始至終未使用過這個例項,則會造成記憶體的浪費。

2.2懶漢模式(執行緒不安全)

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){} 
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
        return instance;  
    }  
}  
複製程式碼

懶漢模式宣告瞭一個靜態物件,在使用者第一次呼叫的時候初始化。這雖然節約了資源,但第一次載入時需要例項化,反應稍微慢一些,而且在多執行緒時不能正常工作。

2.3 懶漢模式(執行緒安全)

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
} 
複製程式碼

這種寫法能夠在多執行緒中很好的工作,但每次呼叫getInstance方法時都需要進行同步,這回造成不必要的同步開銷,而且大部分時候我們是用不到同步的。所以不建議用這種模式。

2.4 雙重檢查模式(DCL)

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  
複製程式碼

這種寫法在getInstance方法中對Singleton進行了兩次判空:第一次是為了不必要的同步,第二次是在Singleton等於null的情況下才建立例項。在這裡使用volatile會或多或少的影響效能,但考慮到程式的正確性,犧牲這單效能還是值得的。DCL的有點是資源利用率高。第一次執行getInstance時單例物件才被初始化,效率高。其缺點是第一次載入時速度反應稍慢一些,在高併發環境下也有一定的缺陷。DCL雖然在一定程度上解決了資源的 消耗和多餘的同步、執行緒安全等問題,但其還是在某些情況會出現失效的問題,也就是DCL失效。這裡建議用靜態內部類單例模式來替代DCL。

2.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類時並不會初始化sInstance,只有第一次呼叫getInstance方法時虛擬機器載入SingletonHolder並初始化sInstance。這樣不僅能保證執行緒安全,也能保證Singleton類的唯一性。所以,推薦使用靜態內部類單例模式。

2.6 列舉單例

public enum Singleton {  
    INSTANCE;  
    public void doSomeThing() {  
    }  
}  
複製程式碼

預設列舉例項的建立時執行緒安全的,並且在任何情況下都是單例。在上面講的擊中單例模式實現中,有一種情況下其會重新建立物件那就是反序列化:講一個單例例項物件寫到磁碟在讀回來,從而獲得了一個例項。反序列化操作提供了readResolve方法,這個方法可以讓開發人員控制物件的反序列化。在上述幾個方法例項中,如果要杜絕單例物件被反序列化時重新生成物件,就必須加入如下方法:

private Object readResolve() throws ObjectStreamException{
    return singleton;
}
複製程式碼

列舉單俐的有點是簡單,但大部分應用開發很少使用列舉,其可讀性並不是很高。到這裡單例模式的6中寫法都介紹完了。至於選擇哪種形式的單俐模式,則取決於你的專案本身情況:是否為複雜的高併發環境,或者是否需要控制單例物件的資源消耗。

3單例模式的使用場景

在一個專案中,要求一個類有且僅有一個物件,他的具體使用場景如下:

  • 整個專案需要一個共享訪問點或共享資料
  • 建立一個兌現更需要耗費的資源過多,比如訪問I/O或者資料庫等資源
  • 工具類物件

參考:《Android進階之光》 作者:劉望舒

相關文章