在Java編程中,Singleton 模式可算得上是應用最廣泛的一種設計模式了。它大量得被用于各種流行的框架中,包括最基本的 JDK 代碼中。從代碼來說, Singleton 的實現很簡單,但是真正用好并不容易,幾年的開發設計工作中經常都遇到由于沒有弄清實例的單態或非單態性,以及不恰當得使用單態導致的問題。
基本概念:
從分類來說,Singleton 屬于創建型(Construction)模式,最基本的目標是要保證在一個JVM中,一個 Class 最多只有一個實例存在。而要達成這個目標背后的驅動力是: 減少不必要的資源和時間的開銷。
- 資源:在內存中每個對象實例都要占據一定空間,當程序不需要為每個線程在該對象中保存不同的狀態時,就可以用Singleton 模式,永遠只用一個對象實例為所有線程服務。
- 時間:在JVM中每次構造一個對象實例都是有一定時間消耗的,用Singleton 模式可以幫助程序提高性能,只有第一次初始化實例時需要時間消耗,以后每次引用的時間消耗都幾乎為零。
Singleton 最根本的價值就在于“節省資源”!盡管這種設計被廣泛使用,但是在單線程或低并發環境中,它在性能和資源節省上帶來的價值并不大,越是高并發的多線程環境,Singleton 所能帶來的價值越明顯!(當然前提是你能夠正確使用它)
反駁:
1. 現在有些文章(其實是老外N年前就討論過的問題)在評論到底還需不需要用 Singleton 模式,在討論 Singleton 是不是邪惡的:) 其實辯證地看,世間被就沒有什么東西是絕對好或不好,關鍵看你怎么利用它。如果程序員對 Singleton 模式理解不深,不恰當使用,確實會導致嚴重問題,但這不代表這個模式是不該存在的。就如同C語言的指針,很多人用錯,但是不能說指針就是一個錯誤的設計。是天使還是魔鬼都取決與利用它的人。
2. 現在有些人說 Singleton 已經過時了,不需要了,仿佛 IOC 模式已經把一切關于 bean 創建的問題都解決了。我不這樣認為。也許現在程序員或架構師已經不需要自己實現 Singleton 了,但這只是因為 Singleton 的實現已經被一些成熟的框架包辦,程序員不需要自己去關心了,并不是說 Singleton 不存在。我們仍然需要控制對象實例的數目來達到節省資源,減少開銷的目的!如果程序員對 Singleton 沒有足夠的理解,也很難正確有效得使用幫我們包辦一切的框架,如 Spring。
模式實現:
經過人們對 Singleton 多年的使用,通常有兩種公認的線程安全的實現(并非全部):
1. Lazy initialization:
提供一個synchronized getInstance() 方法來檢查對象實例是否已創建。如果是,直接返回引用;如果不是,創建實例并返回。并將缺省的構造器方法定義為 private:
class Singleton {
private static Singleton _instance;
private int _state;
private Singleton() {
_state = 0;
}
public static synchronized Singleton getInstance() {
if (_instance == null)
_instance = new Singleton();
return _instance;
}
}
2. Aggressive initialization:
拋棄 synchronized,而使用靜態屬性,在類載入時立即初始化,同樣需要把缺省的構造器方法定義為 private:
class Singleton {
private int _state;
private static Singleton _instance = new Singleton();
private Singleton() {
_state = 0;
}
public static Singleton getInstance() {
return _instance;
}
}
問題分析:
1. 上面的第一種實現雖然可行,但是有一個缺點就是多余的 synchronized 消耗:事實上 Singleton 的實例化只有在第一次實際進行 new Singleton() 的時候需要 synchronized,從那以后每次調用 getInstance() 方法只需要簡單返回 _instance 引用就可以了,而此時 synchronized 需要的消耗就成了浪費!
2. 我曾經在不止一次的項目中看到過如下的代碼來實現 Singleton,這是為了解決上面的 synchronized 浪費:
class Singleton {
private static Singleton _instance;
private int _state;
private Singleton() {
_state = 0;
}
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
_instance = new Singleton();
}
}
return _instance;
}
}
這個實現在單線程環境下不會出問題,但是放到并發的環境中是有問題的,線程并不真正安全。多個線程有可能同時進入 if(_instance == null) 內部,而導致程序實際創建出多個對象實例!
3. 還有一種 Double-checked locking 的實現,試圖解決上面兩個問題:
class Singleton {
private static Singleton _instance;
private int _state;
private Singleton() {
_state = 0;
}
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
可以看到這個實現煞費苦心,在 synchronized 內部再判斷一次 if(_instance == null)。于是在單線程和低并發環境下,這個實現很難出問題了,但是到了足夠高并發的環境中,線程再次變得不安全。這個問題是由 Java 平臺的內存模式引起的,也與不同的 JIT 編譯器的編譯方式有關,稱之為“out-of-order writes”:一個實例 _instance 有可能在 new Singleton() 沒有完全初始化的時候就已經不再是 null,于是并發線程可能得到一個沒有完全初始化的實例,從而引起錯誤。
有很多關于 Double-checked locking 或 out-of-order writes 的分析文章,推薦:
Double-checked locking and the Singleton pattern
4. 看過一些關于 Singleton 的文章,提到 Singleton 的另一個用途,可以用來保持全局狀態,如網站計數器。確實 Singleton 可以幫助我們達到這個目的,但是仔細想想,其實任意一個類靜態變量都可以達到這個目的,Singleton不是必需。而且考慮 Singleton 模式使用中帶有的陷阱,它并不是一個好的辦法來達到保持全局狀態的目的。
5. Singleton 模式只能保證在單 JVM 中只創建一個對象實例!相同的代碼一旦部署到集群或分布式環境中就可能出錯,Singleton 完全失效了!在分布式環境中,StatelessSession bean 是一個好的選擇。
6. 綜上所述,Singleton 模式有它特定的適用場合,達到特定的目的(節省資源!)。除非必要,盡量不要用 Singleton!