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進階之光》 作者:劉望舒