Java單例模式實現,一次性學完整,面試加分項

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 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@61e4705borg。yd。singleton。Singleton03@50134894false

再看看列舉類的測試

@Testpublic void reflectEnumTest() throws Exception { EnumSingleton01 s = EnumSingleton01。INSTANCE; // 拿到所有的建構函式,包括非public的 Constructor 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。() 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)

結論:透過反射,單例模式的私有構造方法也能構造出新物件。不安全。而列舉類直接拋異常,說明列舉類對反射是安全的。

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

結論:說明列舉類序列化安全。

綜上,可以得出結論:列舉是實現單例模式的最佳實踐。

反射安全

序列化/反序列化安全

寫法簡單