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