1. 程式人生 > 其它 >Head First 設計模式筆記 4.單例模式

Head First 設計模式筆記 4.單例模式

技術標籤:Head First設計模式設計模式軟體架構

單例模式比較簡單,用於建立全域性唯一的一個物件。這裡直接貼出它的定義

單例模式保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

有什麼用呢?很多物件只有一個,比如工作管理員,檔案。假如有好幾個物件,就會出現BUG。

那麼,利用全域性靜態變數實現單一物件如何呢?

  • 全域性靜態變數確實能夠保證全域性訪問,但是無法保證全域性只有一個物件
  • 假如直接賦值給一個全域性變數,就意味著要在一開始就初始化它,如果它很大,然而執行過程中又沒有使用到,就會導致空間的浪費。

單例模式的實現

將建構函式訪問設計為private會怎麼樣?如下程式碼所示

public class Myclass{
	private Myclass(){};
}

這個類能否被例項化呢?

很明顯做不到,因為它的建構函式被設為private的,也就是說只有該類的例項才能呼叫它,然而不用建構函式又無法例項化物件。

我們再加入一個靜態函式,用於呼叫這個建構函式。

public class Myclass{
	private Myclass(){}
	public static Myclass getInstance(){
		return new Myclass();
	}
}

這樣就可以例項化該類了,再稍作修改,就可以得到一個只能被例項化一次的類。

懶漢式,執行緒不安全

// NOTE: This is not thread safe!

public class Singleton {
	private static Singleton uniqueInstance;
 	//私有化的建構函式,保證它不會被例項化
	private Singleton() {}
 
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
}

然而這麼做並非執行緒安全的。當多個執行緒訪問getInstance()

方法時,就可能會例項化出多個物件。

懶漢式,執行緒安全

一個粗暴的解決方法,我們給getInstance()方法加上synchronized關鍵字。

public class Singleton {
	private static Singleton uniqueInstance;
 	//私有化的建構函式,保證它不會被例項化
	private Singleton() {}
 
	public static synchronized Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
}

然而,實際上我們只需要在第一次呼叫getInstance()才需要同步,這樣加上同步後會引入不必要的效率降低。假如getInstance()方法需要被頻繁地呼叫,那麼會大大降低效率。

餓漢式,執行緒安全

假如建立一個物件的消耗不大的話,我們可以考慮直接在初始化的時候例項化物件。如下所示

public class Singleton {
	private static Singleton uniqueInstance = new Singleton(); 
 	//私有化的建構函式,保證它不會被例項化
	private Singleton() {}
 
	public static Singleton getInstance() {
		return uniqueInstance;
	}
}

這種在初始化時直接例項化單例的實現方式,我們叫它“餓漢式”,主要用於物件被頻繁建立並且建立消耗不大的情況。與之對應的前面的直到使用才建立單件的實現方式稱為“懶漢式”。

雙檢鎖/雙重校驗鎖

那麼當單例建立消耗比較大,而且可能需要建立頻繁,也可能不會被建立,該怎麼辦呢?用synchronized修飾方法會由於同步造成不必要的效能下降,用餓漢式建立方式又會讓初始化就例項化物件,而這個物件可能從未被例項化。

雙檢鎖/雙重校驗鎖可以幫助我們解決這個問題。我們可以先檢查物件是否被建立,如果沒有被建立,我們才進行同步。

//
// Danger!  This implementation of Singleton not
// guaranteed to work prior to Java 5
//

public class Singleton {
	private volatile static Singleton uniqueInstance;
 	// volatile確保當uniqueInstance被例項化,執行緒訪問它是執行緒安全的
	private Singleton() {}
 
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
		// 假如單件為空,才會對整個類進行加鎖
			synchronized (Singleton.class) {
				if (uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

很巧妙的方法,遺憾的是在1.4版本以前的Java版本中,不支援volatile修飾字。所以用的時候注意Java的版本。

單例模式的黑暗面

使用單例模式的類是無法被繼承的,因為它的建構函式是private修飾的,無法被擴充套件。然而將privata改為protected又會破壞單例模式,別的類也可以例項化它了。假如一個設計大量使用了單價模式,那麼很有可能是有問題的。因為一般情況下它的使用場合不多。