Head First 設計模式筆記 4.單例模式
單例模式比較簡單,用於建立全域性唯一的一個物件。這裡直接貼出它的定義
單例模式保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
有什麼用呢?很多物件只有一個,比如工作管理員,檔案。假如有好幾個物件,就會出現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
又會破壞單例模式,別的類也可以例項化它了。假如一個設計大量使用了單價模式,那麼很有可能是有問題的。因為一般情況下它的使用場合不多。