前言:說起單例模式,可能大家都熟悉,可以說是設計模式中出現頻率最高的一個,為了徹底弄清單例,在這裡我將說明何為單例,單例模式的演變,已經和靜態類之間的區別等。
何為單例,就是在一個應用程序中只能有一個實例,就是保證對象只能被new一次。
懶漢我覺得這個名字很形象,就是很懶,所以別的對象加載,它就不加載,你調用我的時候我在加載。比喻hibernate中也有懶模式。ok我們開始吧
一天小明去面試,面試官說,你給我寫個單例模式,小明一想這實在太簡單了不暇思索很快寫出來了如下的單例模式
1 public class Singleton { 2 private static Singleton singleton; 3 public static Singleton getSingleton() 4 { 5 if(singleton==null) 6 { 7 singleton=new Singleton(); 8 } 9 return singleton; 10 } 11 }
然後面試官一看說:你這在高並發的時候有可能會產生多個singleton實例,小明一想怎麼會呢,面試官解釋說,如果有2個線程T1,T2同時執行,當T1執行到第6行的時候,時間片段到,系統開始讓T2執行,執行到第9行,然後T1又開始執行,因為T1已結做過判斷此時並不知道singleton已結被實例化,所以singleton此時再次被實例化,這樣你系統就有2個singleton對象,那還是單例嗎,小明恍然大悟,這個我能解決,馬上又寫出下面這個
1 public class Singleton { 2 private static Singleton singleton; 3 4 public synchronized static Singleton getSingleton() 5 { 6 if(singleton==null) 7 { 8 singleton=new Singleton(); 9 } 10 return singleton; 11 }
面試官一看,加上了線程同步,這個時候確實能保證線程安全問題,但是又提出了疑問,如果現在singleton已經被實例化了,如果10個線程同時訪問,每次都要等待那麼勢必造成性能極大的消耗,你有沒有別的方案解決問題,小明思考一分鐘又寫下了下面一段代碼
1 public class Singleton { 2 private static Singleton singleton; 3 4 public static Singleton getSingleton() 5 { 6 if(singleton==null) 7 { 8 synchronized (Singleton.class) { 9 if(singleton==null) 10 { 11 singleton=new Singleton(); 12 } 13 } 14 } 15 return singleton; 16 }
面試官一看,果真在上面一段代碼的基礎上提升了不少性能,減少了不必要的等待,但是仔細一看說你這代碼有點問題,並不能保證線程的安全,小明說怎麼說呢,然後面試官解釋說:如果有T1,T2兩個線程,T1線程運行第六行發現singleton==null,就進入第8行,開始對singleton進行實例化,因為實例化中分為三步,第一步為對象開辟內存空間,第二步為對象初始化,第三步是把這個內存地址賦給singleton,但是因為java的內存模式允許無序寫入,這樣一來會導致第二步和第三步位置調換,那麼這樣一來就壞了,如果先允許第一步和第三步了,但是此時並沒有對對象進行初始化,恰恰在此時T2進入了第6行,經過判斷singleton不為null,那麼就會返回一個沒有被初始化的對象。小明聽了覺得對啊,他說我把內存模式改為不允許無序寫入不就行了嗎,於是就把代碼修改為
1 public class Singleton { 2 private volatile static Singleton singleton;//表示有序寫入 3 4 public static Singleton getSingleton() 5 { 6 if(singleton==null) 7 { 8 synchronized (Singleton.class) { 9 if(singleton==null) 10 { 11 singleton=new Singleton(); 12 } 13 } 14 } 15 return singleton; 16 }
注釋1:對象實例化三步我這裡做一個比喻,某個公司給員工分配一間寢室(指的就是在堆中開辟了空間)然後呢給房子進行一些標配比喻分個空調、洗衣機什麼的(對象初始化),在然後呢把鑰匙給到員工手裡(對象進行賦值)。
面試官又問你知道餓漢模式怎麼寫的嗎,小明一聽:哦餓漢,不就是很著急自己馬上進行實例化,生怕自己無法實例化嗎,這個簡單馬上寫了一個餓漢
public class Singleton { private static Singleton singleton=new Singleton(); public static Singleton getSingleton() { return singleton; } }
面試官一看確實不錯。
小明一看上面的模式,自己突發奇想,餓漢模式著急創建對象,在加載時候消耗性能,而懶漢模式又存在線程安全問題(優化��沒有了)能不能結合一下呢,突然告訴面試官我還有一個比較好的方式來實現,然後他寫了下面代碼
1 public class Singleton { 2 private static class SingletonManager{ 3 private final static Singleton SINGLETON=new Singleton(); 4 public final static Singleton getSingleton() 5 { 6 return SingletonManager.SINGLETON; 7 } 8 }
面試官一看,不錯不錯,既保證了懶加載,同時也保證了線程安全問題。
面試官又問小明,那麼你知道使用場景嗎,小明想了想說,既然在應用程序中只有一個單例,那麼勢必是用於共享資源,比喻數據庫連接池,線程池等都可以用單例模式。
面試官繼續問:靜態類同樣也是產生一個對象,和單例具有高度相似你知道他們區別嗎,小明回答說
1:面向對象中有三大特性繼承,封裝和多態,但是靜態類是不可以繼承的,所以從oo角度來說靜態類並不符合面向對象,他們的類是不可以被覆蓋,所以靈活性要比單例差的多
2:由於靜態類的特殊他在編譯器已經進行實例化了並不能提供懶加載模式
3:對於項目中如果進行單元測試,由於方法不能覆蓋同樣為測試帶來了困難
4:由於靜態類在編譯器已經都被實例化,所以要比單例性能要快,如果只需要執行一些靜態方法這個時候可以采用靜態類
單例模式看似簡單,其實用起來還要考慮到很多問題,現在我把這個過程基本總結了,當然可能還有不足之處,歡迎指正。