如何建立一個物件(二、單例)
為什麼需要單例模式
在應用程式中,經常會用到單例模式,即這個類只能存在一個物件例項。
那麼為什麼需要這種模式,我們在一個程式應用中,只需要建立一次性的物件例項以節省記憶體資源,避免重複建立的開銷,以便後面使用可以更快的訪問。
如何寫一個單例模式
單例作為所有設計模式中最簡單的設計模式之一,其建立是非常簡單的。
餓漢式單例
#code 餓漢式單例-不推薦 public final class HungrySingleton { private byte[] data = new byte[1024]; private static HungrySingleton instance = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return instance; } }
以上就是一個典型的餓漢式單例模式,在我們呼叫HungrySingleton. getInstance()方法時,不僅僅獲取了一個已經建立的物件,並且獲取了已經初始化了的1k的類變數資料。
即便在多執行緒環境下,instance也不會被建立兩次,因為在類初始化的時候被收集進
懶漢式單例
#code 懶漢式單例-不推薦 public final class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } public static synchronized LazySingleton getInstance() { if (null == instance) { instance = new LazySingleton(); } return instance; } }
上面這一段程式碼其實就是懶漢式單例,我們在真正呼叫getInstance()方法的時候才去建立這個例項,這個類所需的資源到這個時候才會被開闢。我們同時也使用synchronized來保證多執行緒環境下只有一份例項。
看起來很美妙,但可惜的是,這個方式也同樣不被推薦。原因也很簡單,因為我們在使用getInstance()的時候是同步的,意味著每個呼叫該方法的執行緒都必須阻塞等待其他執行緒呼叫完成,這一點就很耗費效能。
Double Check
#code 雙重檢查式單例-不推薦 public final class DCSingleton { private static volatile DCSingleton instance; private DCSingleton() { } public static DCSingleton getInstance(){ if (null == instance){ synchronized (DCSingleton.class){ if (null == instance){ instance = new DCSingleton(); } } } return instance; } }
在我很長的一段時間內以來,在建立單例例項的時候都喜歡使用這種方式。它既保證了執行緒安全,也保證了延遲載入,同時相比懶漢式單例的耗費效能,它使用的雙重檢查的技巧很大程度上緩解了效能浪費,而且volatile修飾的instance也不會被指令重排困擾。看上去很完美,從一定程度上來說的確是這樣。
直到我看了doug lea與人合著的那本併發實踐書籍,原文是這樣的:“DCL這種使用方法已經被廣泛的廢棄了——促使該模式出現的驅動力不復存在,因而它不是一種高效的優化措施。延遲初始化佔位類模式能帶來同樣的優勢,並且更容易理解。”這裡的驅動力是指,在新的版本下,無競爭同步的執行速度變快了(以前很慢),JVM的啟動速度也變快了(以前很慢)。
延遲初始化佔位類模式
#code Holder式單例-推薦max
public final class HolderSingleton {
private HolderSingleton() {
}
private static class Holder {
private static HolderSingleton instance = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.instance;
}
}
從執行緒安全、高效能、懶載入來看,這個應該是目前最好的單例設計之一,也是使用最為廣泛的一個。
列舉式
#code Holder式單例-酌情使用
public enum EnumSingleton {
INSTANCE;
EnumSingleton() {
System.out.println("complete init...");
}
public static EnumSingleton getInstance() {
System.out.println("getInstance...");
return INSTANCE;
}
public static void doneTask() {
System.out.println("doneTask...");
}
}
在《Effective Java》那本書中,這個列舉方式實現單例的方式就被極為推薦。列舉方式不允許被繼承,同樣也只是被例項化一次,但是不能夠懶載入。所以讀者可以酌情使用。