Java併發程式設計中的設計模式解析(二)一個單例的七種寫法

leoliu168發表於2018-11-08

Java單例模式是最常見的設計模式之一,廣泛應用於各種框架、中介軟體和應用開發中。單例模式實現起來比較簡單,基本是每個Java工程師都能信手拈來的,本文將結合多執行緒、類的載入等知識,系統地介紹一下單例模式的演變,並體現在7種不同的單例設計中。說到這個,非常像孔乙己裡那個“回字有四種寫法”的梗,不過與封建迂腐文人不同的是,從簡單的單例設計變化,可以看到一個需求演變的過程,看到一個方法不斷完善的過程。

傳送門:Java併發程式設計中的設計模式解析(一)

1. 餓漢式

最簡單的單例設計,優點是執行緒安全,但是因為類載入即初始化例項,加入例項變數比較多的話,會佔用較多的記憶體。

 1 //不允許被繼承
 2 public final class SingletonStarve {
 3     //例項變數, 由於單例物件是靜態的, 在類的載入階段, 就會初始化例項變數
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定義靜態例項物件的時候直接初始化
 7     private static SingletonStarve instance = new SingletonStarve();
 8     //私有化建構函式, 不允許直接new物件
 9     private SingletonStarve() {}
10     //提供公共的方法獲取例項物件
11     public static SingletonStarve getInstance() {
12         return instance;
13     }
14 }

2. 懶漢式

實現了單例設計的懶載入,節省了前期記憶體空間的佔用,但是在多執行緒環境下可能會導致多物件的產生,破壞例項唯一性。

 1 //不允許被繼承
 2 public final class LazySingleton {
 3     //例項變數, 由於單例物件是靜態的, 在類的載入階段, 就會初始化例項變數
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定義靜態例項物件, 不直接初始化
 7     private static LazySingleton instance = null;
 8     //私有化建構函式, 不允許直接new物件
 9     private LazySingleton() {}
10     //提供公共的方法獲取例項物件
11     public static LazySingleton getInstance() {
12         if(null == instance) {
13             instance = new LazySingleton();
14         }
15         return instance;
16     }
17 }

3. 懶漢式+同步鎖

通過使用synchronized關鍵字使getInstance方法變為同步方法,從而確保執行緒安全,但帶來了一定的效能問題。

 1 //不允許被繼承
 2 public final class SyncLazySingleton {
 3     //例項變數, 由於單例物件是靜態的, 在類的載入階段, 就會初始化例項變數
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定義靜態例項物件, 不直接初始化
 7     private static SyncLazySingleton instance = null;
 8     //私有化建構函式, 不允許直接new物件
 9     private SyncLazySingleton() {}
10     //提供公共的方法獲取例項物件, 通過synchronized修飾為同步方法
11     public static synchronized SyncLazySingleton getInstance() {
12         if(null == instance) {
13             instance = new SyncLazySingleton();
14         }
15         return instance;
16     }
17 }

4. Double-Check

推薦使用:Double-Check單例模式,通過兩次非空判斷,並且對第二次判斷加鎖,確保了多執行緒下的單例設計安全,同時保證了效能。

注意:Double-check有可能因為JVM指令重排的原因,導致空指標異常;使用volatile修飾物件引用,可以確保其可見性,避免異常

 1 //不允許被繼承
 2 public final class VolatileDoubleCheckSingleton {
 3     //例項變數, 由於單例物件是靜態的, 在類的載入階段, 就會初始化例項變數
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定義靜態例項物件, 不直接初始化
 7     //通過volatile, 避免指令重排序導致的空指標異常
 8     private static volatile VolatileDoubleCheckSingleton instance = null;
 9     Connection conn;
10     Socket socket;
11     //私有化建構函式, 不允許直接new物件
12     //由於指令重排序, 例項化順序可能重排, 從而導致空指標,使用volatile關鍵字修飾單例解決
13     private VolatileDoubleCheckSingleton() {
14         //this.conn;
15         //this.socket;
16     }
17     //提供公共的方法獲取例項物件
18     public static VolatileDoubleCheckSingleton getInstance() {
19      20         if(null == instance) {
21             synchronized(VolatileDoubleCheckSingleton.class) {
22                 if(null == instance) {//以下賦值因為不是原子性的,如果不使用volatile使instance在多個執行緒中可見,將可能導致空指標
23                     instance = new VolatileDoubleCheckSingleton();                    
24                 }
25             }
26         }
27         return instance;
28     }
29 }

5. 靜態內部類

推薦使用:通過使用靜態內部類,巧妙地避免了執行緒不安全,並且節省了前期記憶體空間,編碼非常簡潔。

 1 //不允許被繼承
 2 public final class HolderSingleton {
 3     //例項變數
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //私有化構造器
 7     private HolderSingleton() {}
 8     //定義靜態內部類Holder, 及內部例項成員, 並直接初始化
 9     private static class Holder{
10         private static HolderSingleton instance = new HolderSingleton();
11     }
12     //通過Holder.instance獲得單例
13     public static HolderSingleton getInstance() {
14         return Holder.instance;
15     }
16 }

 

6. 列舉類

《Effective Java》中推薦的單例設計模式,缺點是餓漢式,並且對編碼能力要求較高。

 1 //列舉本身是final的, 不允許被繼承
 2 public enum EnumSingleton {
 3     INSTANCE;
 4     //例項變數
 5     @SuppressWarnings("unused")
 6     private byte[] data = new byte[1024];
 7     
 8     EnumSingleton() {
 9         System.out.println("INSTANCE will be initialized immediately");
10     }
11     public static void method() {
12         //呼叫該方法會主動使用EnumSingleton, INSTANCE將會例項化
13     }
14     public static EnumSingleton getInstance() {
15         return INSTANCE;
16     }
17 }

7. 內部列舉類

 1 /*
 2  * 使用列舉類作為內部類實現懶載入
 3  */
 4 public final class LazyEnumSingleton {
 5     private LazyEnumSingleton(){}
 6     private enum EnumHolder{
 7         INSTANCE;
 8         private LazyEnumSingleton instance;
 9         EnumHolder(){
10             this.instance = new LazyEnumSingleton();
11         }
12         private LazyEnumSingleton getLazyEnumSingleton() {
13             return instance;
14         }
15     }
16     public static LazyEnumSingleton getInstance() {
17         return EnumHolder.INSTANCE.getLazyEnumSingleton();
18     }
19 }


相關文章