在最初學習設計模式的時候,課堂上鍾老師介紹了Singleton模式的餓漢式和懶漢式的簡單實現,具體如下:
①餓漢式單例模式:
特點:對象預先加載,在類創建好的同時對象實例生成。getInstance()方法調用反應速度快,代碼簡練。同時是ThreadSafe的。
缺點:每次程序運行不管有沒有用到該實例,都會進行對象實例的初始化,多佔用了內存。
/**
* Hunger Type Singleton
*
* @ThreadSafe
*
*/
class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
// Other functions
}
②懶漢式單例模式:
特點:在需要實例時才進行對象創建,相比餓漢式單例來說更節省內存。
缺點:線程不安全,需要進行同步操作。第一次調用getInstance()時反應不快。
/**
* Lazy Type Singleton
*
* @NoThreadSafe
*
*/
class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// Other functions
}
而要想在多線程環境下安全地使用懶漢式則要使用synchronized來進行同步:
/**
* Thread Safe Lazy Type Singleton
*
* @ThreadSafe
*
*/
class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// Other functions
}
上面的代碼使用了同步方法來保證線程安全,但效率上要差一些。應該儘量避免使用synchronized來標記方法。轉而使用同步方法塊來進行同步,還要儘可能使同步方法中的代碼儘量少,不將某些執行時間長、甚至可能阻塞的操作(如I/O等待)放到同步方法塊中。
更改後的代碼如下:
/**
* Thread Safe Lazy Type Singleton
*
* @ThreadSafe
*
*/
class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
// Other functions
}
上面的代碼依然能夠再次優化。因爲懶漢式和餓漢式的區別在於對象實例在何時創建,當對象實例創建之後,我們不再需要進行同步了。
多線程的懶漢式:雙檢鎖Double-Check Locking,簡稱DCL:
特點:減少了懶漢式單例模式的線程同步次數。
/**
* Double Check Locking Singleton
*
* @ThreadSafe
*
*/
class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// Other functions
}
之所以將instance聲明爲volatile變量,是爲了防止Java即時編譯器進行指令重排序,從而導致無法安全地使用DCL。這裏要注意,只能在JDK1.5或更高版本安全執行上述代碼,因爲volatile屏蔽指令重排序地語義在JDK1.5中才被完全修復。較低版本的JDK中即使使用volatile仍然存在重排序地問題(《深入理解Java虛擬機》Page326)。
③IoDH(Initializationon Demand Holder)式單例模式:
餓漢式單例類不能實現延遲加載,不管將來用不用始終佔據內存;懶漢式單例類線程安全控制煩瑣,而且性能受影響。可見,無論是餓漢式單例還是懶漢式單例都存在這樣那樣的問題。IoDH則是一種將餓漢式單例和懶漢式單例的優點結合的更好的單例實現方式。
在IoDH中,我們在單例類中增加一個靜態(static)內部類,在該內部類中創建單例對象,再將該單例對象通過getInstance()方法返回給外部使用,代碼如下:
//Initialization on Demand Holder
class Singleton {
private Singleton() {
}
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
public static void main(String args[]) {
Singleton s1, s2;
s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1==s2);
}
}
由於靜態單例對象沒有作爲Singleton的成員變量直接實例化,因此類加載時不會實例化Singleton,第一次調用getInstance()時將加載內部類HolderClass,在該內部類中定義了一個static類型的變量instance,此時會首先初始化這個成員變量,由Java虛擬機來保證其線程安全性,確保該成員變量只能初始化一次。由於getInstance()方法沒有任何線程鎖定,因此其性能不會造成任何影響。
通過使用IoDH,我們既可以實現延遲加載,又可以保證線程安全,不影響系統性能,不失爲一種最好的Java語言單例模式實現方式。
IoDH也有缺點:IoDH的實現與編程語言本身的特性相關,很多面嚮對象語言不支持IoDH。
④使用Enum來實現單例模式
《Effective Java》作者Joshua Bloch提出[第3條]可以用單元素枚舉來實現Singleton。Joshua Bloch這麼描述:“...單元素的枚舉類型已經成爲實現Singleton的最佳方法”。
coolxing的博客記載了這種方法的例子,以及這種方式如何完美抵禦可能破壞單例性質的三種方式。
http://coolxing.iteye.com/blog/1446648
前面說明了如何用Java創建多線程安全並且延遲加載的Singleton模式,實現單例模式還需要注意下面幾點(日後再分析):
1.是否可以通過反射獲得新的單例對象?
2.是否可以通過clone方法獲得新的單例對象?
3.是否可以通過序列化方式獲得新的單例對象?
參考資料:
http://www.blogjava.net/Jhonney/archive/2011/04/08/110280.html
http://blog.csdn.net/lovelion/article/details/7420888
其他資料:
單例模式討論篇:單例模式與垃圾回收http://blog.csdn.net/zhengzhb/article/details/7331354