如何創建一個對象(二、單例)


為什么需要單例模式

在應用程序中,經常會用到單例模式,即這個類只能存在一個對象實例。
那么為什么需要這種模式,我們在一個程序應用中,只需要創建一次性的對象實例以節省內存資源,避免重復創建的開銷,以便后面使用可以更快的訪問。

如何寫一個單例模式

  單例作為所有設計模式中最簡單的設計模式之一,其創建是非常簡單的。

餓漢式單例

#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》那本書中,這個枚舉方式實現單例的方式就被極為推薦。枚舉方式不允許被繼承,同樣也只是被實例化一次,但是不能夠懶加載。所以讀者可以酌情使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM