面試的時(shí)候,常常會(huì)被問到這樣一個(gè)問題:請您寫出一個(gè)單例模式(Singleton Pattern)吧,。好吧,寫就寫,,這還不容易,。順手寫一個(gè):
- public final class EagerSingleton
- {
- private static EagerSingleton singObj = new EagerSingleton();
-
- private EagerSingleton(){
- }
-
- public static EagerSingleton getSingleInstance(){
- return singObj;
- }
- }
這種寫法就是所謂的饑餓模式,,每個(gè)對象在沒有使用之前就已經(jīng)初始化了,。這就可能帶來潛在的性能問題:如果這個(gè)對象很大呢?沒有使用這個(gè)對象之前,,就把它加載到了內(nèi)存中去是一種巨大的浪費(fèi),。針對這種情況,我們可以對以上的代碼進(jìn)行改進(jìn),,使用一種新的設(shè)計(jì)思想——延遲加載(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;
- }
- }
這種寫法就是所謂的懶漢模式,。它使用了延遲加載來保證對象在沒有使用之前,,是不會(huì)進(jìn)行初始化的。但是,,通常這個(gè)時(shí)候面試官又會(huì)提問新的問題來刁難一下,。他會(huì)問:這種寫法線程安全嗎?回答必然是:不安全,。這是因?yàn)樵诙鄠€(gè)線程可能同時(shí)運(yùn)行到第九行,,判斷singObj為null,于是同時(shí)進(jìn)行了初始化,。所以,,這是面臨的問題是如何使得這個(gè)代碼線程安全?很簡單,,在那個(gè)方法前面加一個(gè)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;
- }
- }
寫到這里,,面試官可能仍然會(huì)狡猾的看了你一眼,,繼續(xù)刁難到:這個(gè)寫法有沒有什么性能問題呢?答案肯定是有的,!同步的代價(jià)必然會(huì)一定程度的使程序的并發(fā)度降低,。那么有沒有什么方法,,一方面是線程安全的,有可以有很高的并發(fā)度呢,?我們觀察到,,線程不安全的原因其實(shí)是在初始化對象的時(shí)候,所以,,可以想辦法把同步的粒度降低,,只在初始化對象的時(shí)候進(jìn)行同步。這里有必要提出一種新的設(shè)計(jì)思想——雙重檢查鎖(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,;
- }
- }
這種寫法使得只有在加載新的對象進(jìn)行同步,在加載完了之后,,其他線程在第九行就可以判斷跳過鎖的的代價(jià)直接到第15行代碼了,。做到很好的并發(fā)度。
至此,,上面的寫法一方面實(shí)現(xiàn)了Lazy-Load,,另一個(gè)方面也做到了并發(fā)度很好的線程安全,一切看上很完美,。這是,,面試官可能會(huì)對你的回答滿意的點(diǎn)點(diǎn)頭。但是,,你此時(shí)提出說,,其實(shí)這種寫法還是有問題的!,!問題在哪里,?假設(shè)線程A執(zhí)行到了第9行,它判斷對象為空,,于是線程A執(zhí)行到第12行去初始化這個(gè)對象,,但初始化是需要耗費(fèi)時(shí)間的,但是這個(gè)對象的地址其實(shí)已經(jīng)存在了,。此時(shí)線程B也執(zhí)行到了第九行,它判斷不為空,,于是直接跳到15行得到了這個(gè)對象,。但是,這個(gè)對象還沒有被完整的初始化,!得到一個(gè)沒有初始化完全的對象有什么用?。£P(guān)于這個(gè)Double-Checked
Lock的討論有很多,,目前公認(rèn)這是一個(gè)Anti-Pattern,,不推薦使用,!所以當(dāng)你的面試官聽到你的這番答復(fù),他會(huì)不會(huì)被Hold住呢,?
那么有沒有什么更好的寫法呢,?有!這里又要提出一種新的模式——Initialization on Demand Holder. 這種方法使用內(nèi)部類來做到延遲加載對象,,在初始化這個(gè)內(nèi)部類的時(shí)候,,JLS(Java
Language Sepcification)會(huì)保證這個(gè)類的線程安全。這種寫法最大的美在于,,完全使用了Java虛擬機(jī)的機(jī)制進(jìn)行同步保證,,沒有一個(gè)同步的關(guān)鍵字。
- public class Singleton
- {
- private static class SingletonHolder
- {
- public final static Singleton instance = new Singleton();
- }
-
- public static Singleton getInstance()
- {
- return SingletonHolder.instance;
- }
- }
|