面試的時候,常常會被問到這樣一個問題:請您寫出一個單例模式(Singleton Pattern)吧。好吧,寫就寫,這還不容易。順手寫一個:
- public?final?class?EagerSingleton ?
- {??
- ????private?static?EagerSingleton?singObj?=?new?EagerSingleton();??
- ??
- ????private?EagerSingleton(){ ?
- ????}??
- ??
- ????public?static?EagerSingleton?getSingleInstance(){ ?
- ???????return?singObj;
- ????}??
- } ?
這種寫法就是所謂的?饑餓模式,每個對象在沒有使用之前就已經初始化了。這就可能帶來潛在的性能問題:如果這個對象很大呢?沒有使用這個對象之前,就把它加載到了內存中去是一種巨大的浪費。針對這種情況,我們可以對以上的代碼進行改進,使用一種新的設計思想——?延遲加載(Lazy-load Singleton)。
- public?final?class?LazySingleton ?
- {??
- ????private?static?LazySingleton?singObj?=?null;??
- ??
- ????private?LazySingleton(){ ?
- ????}??
- ??
- ????public?static?LazySingleton?getSingleInstance(){ ?
- ????????if(null?==?singObj?)?singObj = new LazySingleton();
- ??????????return?singObj;
- ????}??
- } ?
這種寫法就是所謂的?懶漢模式。它使用了延遲加載來保證對象在沒有使用之前,是不會進行初始化的。但是,通常這個時候面試官又會提問新的問題來刁難一下。他會問:這種寫法線程安全嗎?回答必然是:不安全。這是因為在多個線程可能同時運行到第九行,判斷singObj為null,于是同時進行了初始化。所以,這是面臨的問題是如何使得這個代碼線程安全?很簡單,在那個方法前面加一個Synchronized就OK了。
- public?final?class?ThreadSafeSingleton ?
- {??
- ????private?static?ThreadSafeSingleton?singObj?=?null;??
- ??
- ????private?ThreadSafeSingleton(){ ?
- ????}??
- ??
- ????public?static?Synchronized?ThreadSafeSingleton?getSingleInstance(){ ?
- ????????if(null?==?singObj?)?singObj = new?ThreadSafeSingleton();
- ????????????return?singObj;
- ????}??
- } ?
寫到這里,面試官可能仍然會狡猾的看了你一眼,繼續刁難到:這個寫法有沒有什么性能問題呢?答案肯定是有的!?同步的代價必然會一定程度的使程序的并發度降低。那么有沒有什么方法,一方面是線程安全的,有可以有很高的并發度呢?我們觀察到,線程不安全的原因其實是在初始化對象的時候,所以,可以想辦法把同步的粒度降低,只在初始化對象的時候進行同步。這里有必要提出一種新的設計思想——?雙重檢查鎖(Double-Checked Lock)。
- public?final?class?DoubleCheckedSingleton ?
- {??
- ????private?static?DoubleCheckedSingletonsingObj?=?null;??
- ??
- ????private?DoubleCheckedSingleton(){ ?
- ????}??
- ??
- ????public?static?DoubleCheckedSingleton?getSingleInstance(){ ?
- ????????if(null?==?singObj?) {
- ??????????????Synchronized(DoubleCheckedSingleton.class){
- ?????????????????????if(null?==?singObj)
- ???????????????????????????singObj = new?DoubleCheckedSingleton();
- ??????????????}
- ?????????}
- ???????return?singObj;
- ????}??
- } ?
這種寫法使得只有在加載新的對象進行同步,在加載完了之后,其他線程在第九行就可以判斷跳過鎖的的代價直接到第15行代碼了。做到很好的并發度。
至此,上面的寫法一方面實現了Lazy-Load,另一個方面也做到了并發度很好的線程安全,一切看上很完美。這是,面試官可能會對你的回答滿意的點點頭。但是,你此時提出說,其實這種寫法還是有問題的!!問題在哪里?假設線程A執行到了第9行,它判斷對象為空,于是線程A執行到第12行去初始化這個對象,但初始化是需要耗費時間的,但是這個對象的地址其實已經存在了。此時線程B也執行到了第九行,它判斷不為空,于是直接跳到15行得到了這個對象。但是,這個對象還?沒有被完整的初始化!得到一個沒有初始化完全的對象有什么用!!關于這個Double-Checked Lock的討論有很多,目前公認這是一個Anti-Pattern,不推薦使用!所以當你的面試官聽到你的這番答復,他會不會被Hold住呢?
那么有沒有什么更好的寫法呢?有!這里又要提出一種新的模式——?Initialization on Demand Holder.?這種方法使用內部類來做到延遲加載對象,在初始化這個內部類的時候,JLS(Java Language Sepcification)會保證這個類的線程安全。這種寫法最大的美在于,完全使用了Java虛擬機的機制進行同步保證,沒有一個同步的關鍵字。
- public?class?Singleton ???
- {????
- ????private?static?class?SingletonHolder????
- ????{????
- ????????public?final?static?Singleton?instance?=?new?Singleton();????
- ????}????
- ???
- ????public?static?Singleton?getInstance()????
- ????{????
- ????????return?SingletonHolder.instance;????
- ????}????
- } ?
至此,本文完。提供一些鏈接For your reference:
Double-Checked Lock:?http://en.wikipedia.org/wiki/Double-checked_locking
Initialzation on Demand Holder:??http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
原文地址:http://blog.sina.com.cn/s/blog_75247c770100yxpb.html