單例模式是設計模式中使用最爲廣泛的一種模式。屬於對象建立模式,它能夠確保系統中一個類只產生一個實例。這樣的行爲能帶來兩大好處:java
在實際應用中,不少時候有一些對象咱們只須要一個,例如:線程池(threadpool)、緩存(cache)、註冊表(registry)、日誌對象等等,這個時候把它設計爲單例模式是最好的選擇。數據庫
public class Singleton01 { private static Singleton01 instance; /** * 私有構造方法 */ private Singleton01(){} public static Singleton01 getInstance() { if(instance == null) { instance = new Singleton01(); } return instance; } }
這種寫法實現延遲加載,但線程不安全。禁止使用!設計模式
public class Singleton02 { private static Singleton02 instance; /** * 私有構造方法 */ private Singleton02(){} public static synchronized Singleton02 getInstance() { if(instance == null) { instance = new Singleton02(); } return instance; } }
這種寫法實現延遲加載,且增長synchronized來保證線程安全,但效率過低。不建議使用緩存
public class Singleton03 { private volatile static Singleton03 instance; /** * 私有構造方法 */ private Singleton03(){} public static Singleton03 getInstance() { if(instance == null) { synchronized (Singleton03.class) { if (instance == null) { instance = new Singleton03(); } } } return instance; } }
使用到了volatile機制。這個是第二種方式的升級版,俗稱雙重檢查鎖定。既保證了效率,又保證了安全。安全
public class Singleton03 { private static Singleton03 instance = new Singleton03(); /** * 私有構造方法 */ private Singleton03(){} public static synchronized Singleton03 getInstance() { return instance; } }
這種基於類加載機制避免了多線程的同步問題,初始化的時候就給裝載了。但卻沒了懶加載的效果。
這也是最簡單的一種實現。多線程
public class Singleton04 { // 靜態內部類 private static class SingletonHolder { private static final Singleton04 INSTANCE = new Singleton04(); } /** * 私有構造方法 */ private Singleton04(){} public static Singleton04 getInstance() { return SingletonHolder.INSTANCE; } }
這種方式當Singleton04
類被加載時,其內部類並不會被加載,因此單例類INSTANCE
不會被初始化。
只有顯式調用getInstance
方法時,纔會加載SingletonHolder
,從而實例化INSTANCE
。
因爲實例的創建是在類加載時完成,因此天生線程安全。所以兼備了懶加載和線程安全的特性。函數
public enum EnumSingleton01 { INSTANCE; public void doSomething() { System.out.println("doSomething"); } }
模擬數據庫連接:測試
public enum EnumSingleton02 { INSTANCE; private DBConnection dbConnection = null; private EnumSingleton02() { dbConnection = new DBConnection(); } public DBConnection getConnection() { return dbConnection; } }
這種方式是Effective Java
做者Josh Bloch
提倡的方式,它不只能避免多線程同步問題,
並且還能防止反序列化從新建立新的對象。線程
前5種方式實現單例都有以下3個特色:設計
首先,私有化構造器並不保險。由於它抵禦不了反射攻擊
,其次就是序列化從新建立新對象。下面來進行驗證。
@Test public void reflectTest() throws Exception { Singleton03 s = Singleton03.getInstance(); // 拿到全部的構造函數,包括非public的 Constructor<Singleton03> constructor = Singleton03.class.getDeclaredConstructor(); constructor.setAccessible(true); // 構造實例 Singleton03 reflection = constructor.newInstance(); System.out.println(s); System.out.println(reflection); System.out.println(s == reflection); }
輸出結果:
org.yd.singleton.Singleton03@61e4705b org.yd.singleton.Singleton03@50134894 false
再看看枚舉類的測試
@Test public void reflectEnumTest() throws Exception { EnumSingleton01 s = EnumSingleton01.INSTANCE; // 拿到全部的構造函數,包括非public的 Constructor<EnumSingleton01> constructor = EnumSingleton01.class.getDeclaredConstructor(); constructor.setAccessible(true); // 構造實例 EnumSingleton01 reflection = constructor.newInstance(); System.out.println(s); System.out.println(reflection); System.out.println(s == reflection); }
輸出結果:
java.lang.NoSuchMethodException: org.yd.singleton.EnumSingleton01.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at org.yd.singleton.SingletonTest.reflectEnumTest(SingletonTest.java:61) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
結論:經過反射,單例模式的私有構造方法也能構造出新對象。不安全。而枚舉類直接拋異常,說明枚舉類對反射是安全的。
@Test public void serializeTest(){ Singleton03 s = Singleton03.getInstance(); String serialize = JSON.toJSONString(s); Singleton03 deserialize =JSON.parseObject(serialize,Singleton03.class); System.out.println(s); System.out.println(deserialize); System.out.println(s == deserialize); }
輸出結果:
org.yd.singleton.Singleton03@387c703b org.yd.singleton.Singleton03@75412c2f false
結論:序列化先後兩個對象並不相等。因此序列化也是不安全的。
一樣看看枚舉類的測試
@Test public void serializeEnumTest(){ EnumSingleton01 s = EnumSingleton01.INSTANCE; String serialize = JSON.toJSONString(s); EnumSingleton01 deserialize =JSON.parseObject(serialize,EnumSingleton01.class); System.out.println(s); System.out.println(deserialize); System.out.println(s == deserialize); }
輸出結果:
INSTANCE INSTANCE true
結論:說明枚舉類序列化安全。
綜上,能夠得出結論:枚舉是實現單例模式的最佳實踐。