Java單例模式實現,一次性學完整,面試加分項
單例模式是設計模式中使用最為普遍的一種模式。屬於物件建立模式,它可以確保系統中 一個類 只產生 一個例項。這樣的行為能帶來兩大好處:
對於頻繁使用的物件,可以省略建立物件所花費的時間,這對於那些重量級物件而言,是非常可觀的一筆系統開銷。
由於new操作的次數減少,因而對系統記憶體的使用頻率也會降低,這將減輕GC壓力,縮短GC停頓時間。
在實際應用中,很多時候有一些物件我們只需要一個,例如:執行緒池(threadpool)、快取(cache)、登錄檔(registry)、日誌物件等等,這個時候把它設計為單例模式是最好的選擇。
1、單例模式6種實現方法
1)懶漢模式(執行緒不安全)
public class Singleton01 { private static Singleton01 instance; /** * 私有構造方法 */ private Singleton01(){} public static Singleton01 getInstance() { if(instance == null) { instance = new Singleton01(); } return instance; }}
這種寫法實現延遲載入,但執行緒不安全。禁止使用!
2)懶漢模式(執行緒安全)
public class Singleton02 { private static Singleton02 instance; /** * 私有構造方法 */ private Singleton02(){} public static synchronized Singleton02 getInstance() { if(instance == null) { instance = new Singleton02(); } return instance; }}
這種寫法實現延遲載入,且增加synchronized來保證執行緒安全,但效率太低。不建議使用
3)懶漢模式(雙重校驗鎖)
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機制。這個是第二種方式的升級版,俗稱雙重檢查鎖定。既保證了效率,又保證了安全。
4)餓漢模式
public class Singleton03 { private static Singleton03 instance = new Singleton03(); /** * 私有構造方法 */ private Singleton03(){} public static synchronized Singleton03 getInstance() { return instance; }}
這種基於類載入機制避免了多執行緒的同步問題,初始化的時候就給裝載了。但卻沒了懶載入的效果。這也是最簡單的一種實現。
5)靜態內部類
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。由於例項的建立是在類載入時完成,所以天生執行緒安全。因此兼備了懶載入和執行緒安全的特性。
6)列舉(號稱最好)
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提倡的方式,它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件。
2、為什麼說列舉方法是最好的?
前5種方式實現單例都有如下3個特點:
構造方法私有化
例項化的變數引用私有化
獲取例項的方法共有
首先,私有化構造器並不保險。因為它抵禦不了反射攻擊,其次就是序列化重新建立新物件。下面來進行驗證。
1) 反射驗證
@Testpublic void reflectTest() throws Exception { Singleton03 s = Singleton03。getInstance(); // 拿到所有的建構函式,包括非public的 Constructor
輸出結果:
org。yd。singleton。Singleton03@61e4705borg。yd。singleton。Singleton03@50134894false
再看看列舉類的測試
@Testpublic void reflectEnumTest() throws Exception { EnumSingleton01 s = EnumSingleton01。INSTANCE; // 拿到所有的建構函式,包括非public的 Constructor
輸出結果:
java。lang。NoSuchMethodException: org。yd。singleton。EnumSingleton01。
結論:透過反射,單例模式的私有構造方法也能構造出新物件。不安全。而列舉類直接拋異常,說明列舉類對反射是安全的。
2) 序列化驗證
@Testpublic 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@387c703borg。yd。singleton。Singleton03@75412c2ffalse
結論:序列化前後兩個物件並不相等。所以序列化也是不安全的。
同樣看看列舉類的測試
@Testpublic 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);}
輸出結果:
INSTANCEINSTANCEtrue
結論:說明列舉類序列化安全。
綜上,可以得出結論:列舉是實現單例模式的最佳實踐。
反射安全
序列化/反序列化安全
寫法簡單