單例模式是一種經常使用的設計模式、也多是設計模式中代碼量最少的設計模式。可是少並不意味着簡單、想要用好、用對單例、就的費一番腦子了。由於它裏面涉及到了不少Java底層的知識如類裝載機制、Java內存模型、volatile等知識點。數據庫
單例模式屬於23中設計模式中的建立型模式、定義是確保某一個類只有一個實例、並提供一個全局的訪問點。設計模式
具備如下3個特性:
單例要求類只能返回同一對象的引用、必須提供一個靜態獲取該實例的方法
實現能夠經過如下兩步:安全
public class EagetSingleton { private static final EagetSingleton INSANCE = new EagetSingleton(); // 私有化構造函數、防止外部實例化 private EagetSingleton() { } // 提供靜態外部訪問方法 public static EagetSingleton getInstance() { return INSANCE; } }
優勢:寫法簡單、類裝載時就實例化了靜態變量、避免了線程併發問題。
缺點:在類裝載過程當中就實例化了對象、形成了資源浪費。多線程
public class StaticBlockSingleton { private static StaticBlockSingleton INSTANCE = null; static { try { INSTANCE = new StaticBlockSingleton(); } catch (Exception e) { } } // 私有化構造函數、防止外部實例化 private StaticBlockSingleton() { } // 提供靜態外部訪問方法 public static StaticBlockSingleton getInstance() { return INSTANCE; } }
這種方式和上述實現方式基本相同、只是把類實例化的過程放到了靜態代碼塊中來實例化、一樣也是在類裝載過程執行靜態代碼塊、優缺點基本相同可是它能夠在類實例化過程當中作一些額外的操做如異常處理等。併發
public class LazySingleton { private static LazySingleton INSTANCE = null; // 私有化構造函數、防止外部實例化 private LazySingleton() { } // 提供靜態外部訪問方法 public static LazySingleton getInstance() { if (null == INSTANCE) { -------- 1 INSTANCE = new LazySingleton(); ------2 } return INSTANCE; } }
優勢:實現了懶加載、避免了資源的浪費。
缺點:線程不安全、在多線程狀況下當一個線程執行到 1 處的時候、尚未來得及往下執行另外一個線程也到 1 處 這樣兩個線程同時執行 2 處代碼、破壞了單例。函數
public class LazySyncSingleton { private static LazySyncSingleton INSTANCE = null; // 私有化構造函數、防止外部實例化 private LazySyncSingleton() { } // 效率低下 // 提供靜態外部訪問方法 public static synchronized LazySyncSingleton getInstance() { if (null == INSTANCE) { INSTANCE = new LazySyncSingleton(); } return INSTANCE; } }
解決了3中線程不安全的問題、利用synchronized對getInstance()方法加鎖以達到同步訪問。
優勢:線程同步
缺點:效率低下、此方式對整個對象加鎖、每次訪問getInstance() 都須要同步訪問、這種狀況多線程併發效率很是低下、其實咱們只須要在對象還沒實例化前加鎖就能夠了、實例化後就不存在併發問題了。工具
public class DCheckSingleton { private static volatile DCheckSingleton INSTANCE = null; // 私有化構造函數、防止外部實例化 private DCheckSingleton() { } // 提供靜態外部訪問方法 public static DCheckSingleton getInstance() { if (null == INSTANCE) { synchronized (DCheckSingleton.class) { if (null == INSTANCE) { INSTANCE = new DCheckSingleton(); } } } return INSTANCE; } }
解決了4中併發狀況下效率低下的問題。
優勢:線程安全、延遲加載、效率高
涉及到知識點: 1:volatile 關鍵字 確保內存的可見性和有序性。若是不加volatile關鍵字會有什麼狀況? 我知道在對象實例化時INSTANCE = new DCheckSingleton();這一句代碼JVM中並非一步執行的而是分爲三步(1)在棧內存中爲 建立對象的引用指針 INSTANCE (2)在堆內存中開闢一塊空間來存放實例化的對象 new DCheckSingleton(); (3)將INSTANCE指向堆內存空間地址J、VM只保證了代碼執行結果的正確性、並不保證執行順序(這裏涉及到Java內存模型知識點在這就很少說了、感興趣的同窗能夠去了解下JVM一些底層實現原理)因此 1 ,2,3三步也多是1 ,3 ,2 這樣咱們就可能拿到的時一個半成品的對象了。
2: 涉及到類實例化知識點
3: 涉及到Java內存模型
4:涉及到JVM的一些執行優化、指令重排等性能
public class InnerSingleton { private InnerSingleton() { } public static InnerSingleton getInstance() { return InnerClassSingleton.INSTANCE; } private static class InnerClassSingleton{ private static final InnerSingleton INSTANCE = new InnerSingleton(); } }
這種方式和餓漢式的實現機制基本相同、都是利用了類裝載機制來保證線程的安全、它和餓漢式的惟一區別就是實現了懶加載的機制、只有在調用getInstance()方法時纔去進行InnerClassSingleton類的實例化。
優勢:避免了線程不安全,延遲加載,效率高。測試
public enum EnumsSingleton { INSTANCE; @SuppressWarnings("unused") private void method() { System.out.println("------- newInstance"); } }
藉助JDK1.5中添加的枚舉來實現單例模式。不只能避免多線程同步問題,並且還能防止反序列化從新建立新的對象。多是由於枚舉在JDK1.5中才添加、因此在實際項目開發中、不多見人這麼寫過。優化
到這單例幾種實現方式以及每種方式的優缺點都作了一些簡單的介紹、枚舉雖小可是設計的知識點不少。
最後在簡單聊一下如何防止暴力破壞單例。主要介紹兩種方式以及如何來防範這兩種方式。
1: 利用Java的反射方式
EagerSingleton instance = EagerSingleton.getInstance(); Constructor instance2 = instance.getClass().getDeclaredConstructor(); instance2.setAccessible(true); EagerSingleton instance3 = (EagerSingleton) instance2.newInstance(); System.out.println("===" + instance); System.out.println("===" + instance3);
利用Java的反射方式能夠達到爆力破解單例的效果、運行結果我就不在這貼出了有興趣的能夠本身試試instance 和 instance3 確定不是一個對象。
如何來防範這方式? 其實也很簡單Java Security 中爲咱們提供了現成的方法。只須要在私有構造中使用SecurityManager 進行檢查下就能夠代碼以下。
// 私有的構造方法,防止外部實例化 private EagerSingleton() { SecurityManager sm = new SecurityManager(); sm.checkPermission(new ReflectPermission("禁止反射")); }
2: 第二種方式是利用Java 序列化和反序列化來實現
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt")); out.writeObject(instance); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("1.txt")); EagerSingleton readObject = (EagerSingleton) in.readObject(); in.close(); System.out.println("==" + instance); System.out.println("==" + readObject);
如何防範? 很簡單隻須要重寫readResolve() 反方就能夠了
private Object readResolve() { return EagerSingleton.instance; }
兩種暴力破解和防範的方式都介紹完了,感興趣的同志能夠去試試我這裏沒有貼出完整的測試代碼和運行結果。
~~~~~~到這咱們的小單例已經介紹完了,有沒有感到驚訝!!!