設計模式:單例模式
一.
單例模式指確保一個類在任何情況下都絕對只有一個例項,並提供一個全域性訪問點。
二.餓漢模式
public class HungrySingleton { private HungrySingleton(){} private final static HungrySingleton HUNGRY_SINGLETON ; static { HUNGRY_SINGLETON = new HungrySingleton(); } public static HungrySingleton getInstance(){ return HUNGRY_SINGLETON; } }
餓漢式是指類的被載入的時候,就被初始化,並建立單例物件,不會存在訪問安全問題。
缺點:所有的餓漢單例物件都會在專案啟動時初始化,會造成大量記憶體資源浪費。
三.懶漢模式
(1)考慮到餓漢模式的缺點後,加以修改,在類被呼叫的時候,才初始化。
public class LazySimpleSingleton { private LazySimpleSingleton(){} private static LazySimpleSingleton LAZY_SIMPLE_SINGLETON = null; public static LazySimpleSingleton getIn,stance(){ if(null==LAZY_SIMPLE_SINGLETON){ LAZY_SIMPLE_SINGLETON = new LazySimpleSingleton(); return LAZY_SIMPLE_SINGLETON; } return LAZY_SIMPLE_SINGLETON; } }
但是會帶來一個新的問題,就是在多執行緒環境下,有兩個執行緒同一時間進入getInstance方法,同事滿足null=LAZY_SIMPLE_SINGLETON時,會建立兩個物件,然後後建立的會覆蓋先建立的單例物件。
考慮到這個問題後,進一步優化,使用synchronized關鍵字,給方法加鎖。
public class LazySimpleSingleton { private LazySimpleSingleton(){} private static LazySimpleSingleton LAZY_SIMPLE_SINGLETON = null; public synchronized static LazySimpleSingleton getInstance(){ if(null==LAZY_SIMPLE_SINGLETON){ LAZY_SIMPLE_SINGLETON = new LazySimpleSingleton(); return LAZY_SIMPLE_SINGLETON; } return LAZY_SIMPLE_SINGLETON; } }
當一個執行緒呼叫getInstance方法時,另一個執行緒也呼叫該方法,會出現阻塞,直到第一個執行緒執行結束,才繼續呼叫,完美解決了執行緒安全問題。
出現新的問題:
如果執行緒數量暴增,給getInstatnce加鎖,只有一個執行緒執行該方法,其他執行緒全部阻塞等待,使用者體驗不好。新的解決方案--雙重檢查鎖單例寫法應運而生。
(2)雙重檢查鎖單例 (進門安檢一次,閘口再檢查一次)
改造一下寫法:
public class LazyDoubleSingleton { private volatile static LazyDoubleSingleton instance; private LazyDoubleSingleton(){} public static LazyDoubleSingleton getInstance(){ synchronized (LazyDoubleSingleton.class){ if(null==instance){ instance = new LazyDoubleSingleton(); } } return instance; } }
這樣的寫法,其實和上一種寫法差不多,都會造成大量執行緒阻塞。那如果把if條件往上升一級呢,先判斷,再加鎖。
public class LazyDoubleSingleton { private volatile static LazyDoubleSingleton instance; private LazyDoubleSingleton(){} public static LazyDoubleSingleton getInstance(){ if(null==instance){ synchronized (LazyDoubleSingleton.class){ instance = new LazyDoubleSingleton(); } } return instance; } }
經過此次修改後,還是會出現執行緒安全問題。
因為當兩個執行緒同事滿足null==instance條件後,會執行sychronized程式碼塊的程式碼,該物件還是會被建立兩次。
再優化一下,我們在sychronized程式碼塊中再進行物件的非空檢查,這樣該物件就不會被建立兩次。
public class LazyDoubleSingleton { private volatile static LazyDoubleSingleton instance; private LazyDoubleSingleton(){} public static LazyDoubleSingleton getInstance(){ //檢查是否要阻塞 if(null==instance){ synchronized (LazyDoubleSingleton.class){ //檢查是否要建立物件 if(null==instance){ instance = new LazyDoubleSingleton(); } } } return instance; } }
四.靜態內部類單例的寫法
雙重檢查鎖單例這種方式雖然解決了執行緒安全問題和效能問題,但是用到了sychronized總是要上鎖,對效能還是有一些影響。我們可以採用靜態內部類的方式進行優化。
/** * @Author wen.jie * @Description 使用InnerClassSingleton類時,會預設先初始化內部類,如果沒有使用,則內部類不初始化 **/ public class InnerClassSingleton { private InnerClassSingleton(){} public static InnerClassSingleton getInstance(){ return InnerClass.INNER_CLASS_SINGLETON; } private static class InnerClass{ private static final InnerClassSingleton INNER_CLASS_SINGLETON = new InnerClassSingleton(); } }
這種方式兼顧了餓漢單例寫法的記憶體浪費問題和sychronized的效能問題,內部類一定要在方法呼叫之前就被初始化,巧妙的避開了執行緒安全問題。靜態內部類單例寫法真的完美了嗎?(未完待續)