設計模式之單例模式

定義

單例模式從字面就看出來是要確保一個類中只有一個實例,字面上看不出來的是創建實例後,還要自行實例化並向整個系統提供這個實例

動手試試

來寫一個簡單的單例類

public class Singleton{
	private static Singleton singleton=null;
	//限制產生多個對象
	private Singleton(){
	}
	//通過該方法獲得實例對象
	public static Singleton getSingleton(){
		if(singleton==null)
			singleton=new Singleton();
		return singleton
	}
}

這樣的單例類看上去沒什麼問題,實際上如果在低併發的情況下可能也不會出現問題。但如果併發量增加,則可能在內存中實現多個實例。如一個線程A執行到singleton=new Singleton(),但還沒有獲得對象,ei~這時候啊,還有一個線程B也在執行,執行到了singleton==null的判斷。線程A還沒有獲得對象啊,所以線程B的判斷條件也成立,結果呢,A獲得一個對象,B獲得一個對象,內存中有兩個對象!說好的單例呢!!

出現問題要想辦法解決

解決線程不安全,首先想到在get方法前加synchronized關鍵字。

public static synchronized Singleton getSingleton(){
		if(singleton==null)
			singleton=new Singleton();
		return singleton
	}

解決了線程問題,但是這樣synchronized鎖住了整個對象,這樣的用法,在性能上會有所下降,因爲每次調用getInstance(),都要對對象上鎖,事實上,只有在第一次創建對象的時候需要加鎖,之後就不需要了,所以,這個地方需要改進。我們改成下面這個:

public static Singleton getSingleton(){
		if(singleton==null){
			synchronized(singleton){
				if(singleton==null)
					singleton=new Singleton();
			}
		}
		return singleton;
	}

再來分析,有A、B兩個線程同時進入第一個if判斷,A首先進入synchronized代碼塊,由於singleton爲null,所以A執行singleton=new Singleton()。由於JVM內部的優化機制,JVM先畫出了一些分配給Singleton實例的空白內存,並賦值給instance成員(注意此時JVM沒有開始初始化這個實例),然後A離開了synchronized塊。B進入synchronized塊,由於instance此時不是null,因此它馬上離開了synchronized塊並將結果返回給調用該方法的程序。此時B線程打算使用Singleton實例,卻發現它沒有被初始化,於是錯誤發生了。所以程序還是有可能發生錯誤,其實程序在運行過程是很複雜的,從這點我們就可以看出,尤其是在寫多線程環境下的程序更有難度,有挑戰性。我們對該程序做進一步優化:

public class Singleton{
	private static final Singleton singleton=new Singleton();
	//限制產生多個對象
	private Singleton(){
	}
	//通過該方法獲得實例對象
	public static Singleton getSingleton(){
		return singleton
	}
}

JVM內部的機制能保證一個類被加載的時候,這個類的加載過程是線程互斥的。這樣當第一次調用getSingleton的時候,JVM能幫助我們保證singleton只能被創建一次,並且會保證把賦值給singleton的內存初始化完畢,這樣我們就不用擔心上面的問題。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。這樣我們暫時總結一個完美的單例模式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章