如何創建一個對象(二、單例)
為什麽需要單例模式
在應用程序中,經常會用到單例模式,即這個類只能存在一個對象實例。
那麽為什麽需要這種模式,我們在一個程序應用中,只需要創建一次性的對象實例以節省內存資源,避免重復創建的開銷,以便後面使用可以更快的訪問。
如何寫一個單例模式
單例作為所有設計模式中最簡單的設計模式之一,其創建是非常簡單的。
餓漢式單例
#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》那本書中,這個枚舉方式實現單例的方式就被極為推薦。枚舉方式不允許被繼承,同樣也只是被實例化一次,但是不能夠懶加載。所以讀者可以酌情使用。
如何創建一個對象(二、單例)