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