第八篇:單例模式

關於單例模式我並不想做過多解釋,網絡上有一大堆關於它的定義,簡而言之,單例就是指單個對象,整個程序的運行生命週期內某個對象只會生成一次!

這麼做有什麼意義呢?很簡單,你的電腦開機,有多個回收站對象會有什麼後果?你刪掉一個文件它該被放入哪個回收站? 有多個註冊表會怎樣? 程序中有多個數據庫連接池會怎樣? 這些問題留給你去思考吧!

正因爲某些對象的特殊性,整個程序生命週期內只能出現一個它的實例, 又或者,某些對象的創建非常複雜非常消耗資源,且它是不會隨着程序的運行而有所改變的,那麼我們也期望只生產一次這個對象,後面直接拿來複用! 那麼這種只會生成一次某個對象的編碼技巧,我們就將其稱爲單列模式!

網絡上好像說是有6中實現單例的方法,但我個人認爲只有4種!另外2種是網友的代碼演變,壓根算不上... 當然,最有效好用的只有一種,我稍後將代碼貼出來!

OK,讓我們拿出點乾貨來,讓代碼來說話吧!


第一種:餓漢式(所謂餓漢就是指不管有沒有人要使用我,我先把自己給創建出來)

public class Singleton {
	
	/**餓漢式,直接創建對象*/
	private static final Singleton singleton = new Singleton();
	
	/**構造器私有化,這樣別人就無法創建我!*/
	private Singleton() {}
	
	/**必須提供一個讓客戶端獲取自身實例的方法*/
	public static Singleton getInstance(){
		return singleton;
	}
	
	
	public static void main(String[] args) {
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		System.out.println(s1==s2); //===>>>輸出:true
	}
}

如上面代碼所示,我們的Singleton類的實例只可能有一個,不用擔心線程問題,因爲它是隨着類加載而實例化的,而類只會被加載一次(當然,也可以被加載多次,關於這點我打算後面再寫篇讓同一個類加載多次的文章,這裏略過) ; 雖然它是安全的,但也有一個問題,假設這個類的創建非常複雜非常消耗資源,但是由於是餓漢式,不管有沒有真正要使用這個類的實例,它總是會創建一次自己,從而佔用我們的系統資源,假如壓根沒有人用到它,那就會造成資源浪費,由此,下面的懶漢式誕生了!


第二種:懶漢式(所謂懶漢式是指等到有人要使用我時,我就開始創建一次自己)

public class Singleton {
	
	/**懶漢式,等到有人使用時再創建!*/
	private static Singleton singleton ;
	
	/**構造器私有化,這樣別人就無法創建我!*/
	private Singleton() {}
	
	/**必須提供一個讓客戶端獲取自身實例的方法*/
	public static Singleton getInstance(){
		//判斷是否爲空,如果是第一次被人使用,則必定是空
		if( singleton==null ){
			singleton = new Singleton();
		}
		return singleton;
	}
	
	
	public static void main(String[] args) {
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		System.out.println(s1==s2); //===>>>輸出:true
	}
}
如上面代碼所示,我們的Singleton現在不再隨着類的加載而實例化了,它只有等到真正有人用到它時纔會去實例化一次,這歸功於那句if...else...的功能,但也正是由於if...else...這裏的代碼所在,它被引入了線程不安全的隱患,在多個線程同時調度下,假如A,B兩個線程都進入到了getInstance()中,此時singleton爲空,那麼就會被創建2次,這是我們無法容忍的,於是,有了下面的雙重加鎖的代碼實現!


第三種:懶漢式變種(雙重檢查加鎖)

public class Singleton {
	
	/**懶漢式,等到有人使用時再創建!*/
	private static Singleton singleton ;
	
	/**構造器私有化,這樣別人就無法創建我!*/
	private Singleton() {}
	
	/**必須提供一個讓客戶端獲取自身實例的方法*/
	public static Singleton getInstance(){
		if( singleton==null ){
			//加一把鎖,且再進行一次判斷
			synchronized (Singleton.class) {
				//當A線程執行到這裏,if條件成立,singleton將被實例化,
				//A線程執行完synchronized代碼塊,釋放鎖
				//而後B線程允許放行進入到這裏,此時if條件將不再成立!
				if( singleton==null ){
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}

}
如果我們直接在getInstance()方法上加鎖(有人將這種做法也稱之爲一種實現方式...所以有人說有6種實現方式),那麼併發上來,將會造成多個線程同時等待,而將其放入if條件之中,將會大量減少線程等待(因爲由於上一個線程的執行完畢,if條件將不再成立),同時也保證了多線程下該對象實例只會被創建一次! 但無論如何,都會造成線程等待,那麼有沒有一種更好的方式來實現單例呢?既可以不造成線程等待,同時又是安全的,保證只會創建一次對象,同時又是基於懶漢式的創建方式, Ok,終極方法,內部類實現!


第四種:懶漢式變種(內部類實現單例,我認爲是最好的一種)

public class Singleton {
	
	/**構造器私有化,這樣別人就無法創建我!*/
	private Singleton() {}
	
	//使用內部類!
	private static class Inner{
		private static final Singleton singleton = new Singleton();
	}
	
	/**必須提供一個讓客戶端獲取自身實例的方法*/
	public static Singleton getInstance(){
		return Inner.singleton;
	}
	
	
	public static void main(String[] args) {
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		System.out.println(s1==s2); //===>>>輸出true
	}
}
OK,我們來研究一下上面的代碼吧!我們的Inner是一個靜態內部類,就算有一億個線程,它也只會被類加載器裝載一次,而裏面的singleton會隨着Inner的類裝載而被實例化,所以,它絕對是唯一不重複的,這一點,它和餓漢式的效果是一樣的。但和餓漢式不同的時,Inner類只有當我們調用Singleton類的getInstance方法時纔會被類加載器加載,所以,singleton的實例也只會在這個時候被創建,所以它不會造成資源浪費!如果理解不了,給我留言吧,文字我只能說到這啦!


Ok,喝杯咖啡潤潤喉...拜拜...


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