Java中單例模式的多種實現方式與優缺點比較

單例模式確保一個類只有一個實例,并提供全局訪問點,實現方式包括餓漢式線程安全但浪費內存;懶漢式延遲加載但需加鎖;雙重檢查鎖減少同步開銷;靜態內部類結合延遲加載和線程安全;枚舉最簡潔且防反射攻擊。應用場景如線程池、配置管理器、數據庫連接池和日志記錄器等。為防反射破壞,可在構造函數中判斷實例是否存在并拋異常,而枚舉天然防止反射攻擊。與靜態類相比,單例支持繼承多態和延遲加載,適用需要全局實例的場景。

Java中單例模式的多種實現方式與優缺點比較

單例模式,簡單來說,就是確保一個類只有一個實例,并提供一個全局訪問點。實現方式多種多樣,各有千秋,選擇哪種,得看具體應用場景。

Java中單例模式的多種實現方式與優缺點比較

解決方案

Java中單例模式的多種實現方式與優缺點比較

單例模式的核心在于控制實例的創建,防止外部隨意new對象。常見的實現方式包括:

立即學習Java免費學習筆記(深入)”;

Java中單例模式的多種實現方式與優缺點比較

  1. 餓漢式(Eager Initialization):

    直接在類加載的時候就創建實例。線程安全,簡單粗暴,但缺點是如果這個單例一直沒被用到,就浪費了內存。

    public class SingletonEager {     private Static final SingletonEager instance = new SingletonEager();      private SingletonEager() {} // 私有構造函數      public static SingletonEager getInstance() {         return instance;     } }
  2. 懶漢式(Lazy Initialization):

    在第一次使用的時候才創建實例。優點是延遲加載,節省內存。但線程不安全,需要加鎖。

    public class SingletonLazy {     private static SingletonLazy instance;      private SingletonLazy() {}      public static synchronized SingletonLazy getInstance() {         if (instance == null) {             instance = new SingletonLazy();         }         return instance;     } }

    加 synchronized 關鍵字保證了線程安全,但每次獲取實例都要同步,效率較低。

  3. 雙重檢查鎖(double-Checked Locking):

    在懶漢式的基礎上,通過雙重檢查和 volatile 關鍵字來提高效率。

    public class SingletonDoubleCheck {     private volatile static SingletonDoubleCheck instance;      private SingletonDoubleCheck() {}      public static SingletonDoubleCheck getInstance() {         if (instance == null) {             synchronized (SingletonDoubleCheck.class) {                 if (instance == null) {                     instance = new SingletonDoubleCheck();                 }             }         }         return instance;     } }

    volatile 關鍵字防止指令重排序,確保多線程環境下instance的正確初始化。雙重檢查減少了同步的開銷,只有在第一次創建實例的時候才需要同步。不過,早期的jvm版本中,volatile 可能存在問題,導致DCL失效。

  4. 靜態內部類(Static Inner Class):

    利用類加載機制保證線程安全,同時實現延遲加載。

    public class SingletonStaticInner {     private SingletonStaticInner() {}      private static class SingletonHolder {         private static final SingletonStaticInner instance = new SingletonStaticInner();     }      public static SingletonStaticInner getInstance() {         return SingletonHolder.instance;     } }

    當外部類 SingletonStaticInner 被加載時,靜態內部類 SingletonHolder 并不會被加載,只有當調用 getInstance() 方法時,才會加載 SingletonHolder,從而創建單例實例。這種方式既保證了線程安全,又實現了延遲加載,推薦使用。

  5. 枚舉(enum):

    最簡潔的單例實現方式,線程安全,防止反射攻擊和序列化攻擊。

    public enum SingletonEnum {     INSTANCE;      public void doSomething() {         // ...     } }

    枚舉單例是Effective Java作者極力推薦的,它利用JVM保證線程安全和唯一性。

單例模式在多線程環境下如何保證線程安全?

保證線程安全的關鍵在于防止多個線程同時創建實例。餓漢式因為在類加載時就創建了實例,所以天生線程安全。懶漢式需要加鎖,或者使用雙重檢查鎖和 volatile 關鍵字。靜態內部類和枚舉則利用了類加載機制,由JVM保證線程安全。

單例模式有哪些應用場景?

單例模式的應用場景非常廣泛。比如:

  • 線程池: 確保只有一個線程池實例來管理線程資源。
  • 配置管理器: 只有一個配置管理器實例來讀取和管理配置信息。
  • 數據庫連接池: 只有一個數據庫連接池實例來管理數據庫連接。
  • 日志記錄器: 只有一個日志記錄器實例來記錄日志信息。

總之,任何只需要一個全局實例的場景,都可以考慮使用單例模式。

如何防止單例模式被反射破壞?

反射可以繞過私有構造函數,創建多個實例。為了防止反射攻擊,可以在構造函數中進行判斷,如果已經存在實例,則拋出異常。

public class SingletonDoubleCheck {     private volatile static SingletonDoubleCheck instance;      private SingletonDoubleCheck() {         if (instance != null) {             throw new IllegalStateException("Singleton instance already exists.");         }     }      public static SingletonDoubleCheck getInstance() {         if (instance == null) {             synchronized (SingletonDoubleCheck.class) {                 if (instance == null) {                     instance = new SingletonDoubleCheck();                 }             }         }         return instance;     } }

枚舉單例則天然防止反射攻擊,因為JVM會阻止通過反射創建枚舉實例。

單例模式和靜態類的區別?

雖然單例模式和靜態類都可以實現全局訪問,但它們有本質的區別。單例模式是一個類的實例,可以繼承接口和抽象類,可以被多態使用。而靜態類只是一個類的集合,不能被繼承和多態使用。此外,單例模式可以延遲加載,而靜態類在類加載時就被初始化。

在選擇單例模式還是靜態類時,要根據具體的需求來決定。如果需要繼承、多態或者延遲加載,則應該選擇單例模式。如果只是需要一個工具類,則可以選擇靜態類。

? 版權聲明
THE END
喜歡就支持一下吧
點贊12 分享