JavaRush /Java Blog /Random-TW /設計模式:單例

設計模式:單例

在 Random-TW 群組發布
你好!今天我們來仔細看看不同的設計模式,先從 Singleton 模式開始,也稱為「單例」。 設計模式:單例 - 1讓我們記住:我們對設計模式整體了解多少?設計模式是解決許多已知問題的最佳實踐。設計模式通常不依賴任何程式語言。將它們作為一組建議,遵循這些建議,您可以避免錯誤,而不是重新發明輪子。

什麼是單例?

單例是可應用於類別的最簡單的設計模式之一。人們有時會說“這個類別是單例”,意思是這個類別實現了單例設計模式。有時需要編寫一個只能建立一個物件的類別。例如,負責記錄或連接資料庫的類別。單例設計模式描述了我們如何完成這樣的任務。單例是一種做兩件事的設計模式:
  1. 保證一個類別只有一個該類別的實例。

  2. 提供對該類別的實例的全域存取點。

因此,幾乎所有單例模式的實現都有兩個特徵:
  1. 私有構造函數。限制在類別本身之外創建類別物件的能力。

  2. 傳回類別的實例的公共靜態方法。這種方法稱為getInstance. 這是類別實例的全域存取點。

實施方案

單例設計模式有不同的使用方式。每個選項都有其自身的優點和缺點。在這裡,一如既往:沒有理想,但需要為此奮鬥。但首先,讓我們先定義什麼是好的,什麼是壞的,以及哪些指標會影響設計模式的實現評估。讓我們從正面的方面開始。以下是賦予實施多汁性和吸引力的標準:
  • 延遲初始化:當應用程式恰好在需要時載入類別時。

  • 程式碼的簡單性和透明度:當然,衡量標準是主觀的,但很重要。

  • 線程安全:在多線程環境下正常工作。

  • 多執行緒環境中的高效能:共享資源時,執行緒之間的阻塞最少或根本不阻塞。

現在說說缺點。我們列出了顯示實施情況不佳的標準:
  • 非惰性初始化:當應用程式啟動時載入一個類別,無論是否需要它(一個悖論,在IT世界中,最好是惰性的)

  • 程式碼複雜且可讀性差。此指標也是主觀的。我們假設如果血液來自眼睛,那麼實施效果一般。

  • 缺乏線程安全。換句話說,「線程危險」。多執行緒環境下操作不正確。

  • 多執行緒環境中的效能較差:執行緒在共享資源時始終或經常相互阻塞。

程式碼

現在我們準備考慮各種實施選項,列出優點和缺點:

簡單的解決方案

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
最簡單的實作。優點:
  • 程式碼簡單透明

  • 線程安全

  • 多執行緒環境下的高效能

缺點:
  • 不是惰性初始化。
為了修正最後一個缺陷,我們得到了第二個實作:

延遲初始化

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
優點:
  • 延遲初始化。

缺點:
  • 不是線程安全的

實施很有趣。我們可以延遲初始化,但是我們失去了線程安全。沒問題:在第三個實作中,我們同步所有內容。

同步存取器

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
優點:
  • 延遲初始化。

  • 線程安全

缺點:
  • 多線程環境下效能不佳

偉大的!在第三個實作中,我們恢復了線程安全!確實,很慢……現在方法getInstance是同步的,一次只能輸入一個。事實上,我們不需要同步整個方法,而只需要同步初始化新類別物件的部分。但我們不能簡單地將synchronized負責創建新物件的部分包裝在區塊中:這不會提供線程安全性。情況有點複雜。正確的同步方法如下:

雙重檢查鎖定

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
優點:
  • 延遲初始化。

  • 線程安全

  • 多執行緒環境下的高效能

缺點:
  • 低於 1.5 的 Java 版本不支援(1.5 版中修復了 volatile 關鍵字)

我注意到,要使此實施選項正常運作,需要滿足以下兩個條件之一。該變數INSTANCE必須是final, 或volatile。我們今天要討論的最後一個實現是Class Holder Singleton.

類別持有者單例

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
優點:
  • 延遲初始化。

  • 線程安全。

  • 多線程環境下的高效能。

缺點:
  • 為了正確運行,需要確保類別物件的Singleton初始化沒有錯誤。否則,第一個方法呼叫getInstance將以錯誤結束ExceptionInInitializerError,並且所有後續方法呼叫都將失敗NoClassDefFoundError

實施幾乎是完美的。而且是惰性的、線程安全的、快速的。但負號中描述了一個細微差別。 Singleton模式的各種實現比較表:
執行 延遲初始化 線程安全 多執行緒速度 什麼時候使用?
簡單的解決方案 - + 快速地 絕不。或延遲初始化不重要時。但永遠不會更好。
延遲初始化 + - 不適用 總是在不需要多執行緒時
同步存取器 + + 慢慢地 絕不。或當多執行緒工作的速度並不重要時。但永遠不會更好
雙重檢查鎖定 + + 快速地 在極少數情況下,您需要在建立單例時處理異常。(當 Class Holder Singleton 不適用時)
類別持有者單例 + + 快速地 總是在需要多執行緒並且保證建立單例類別物件時不會出現問題。

單例模式的優點和缺點

一般來說,單例完全按照預期執行:
  1. 保證一個類別只有一個該類別的實例。

  2. 提供對該類別的實例的全域存取點。

然而,這種模式有缺點:
  1. Singleton 違反了 SRP(單一職責原則)——Singleton 類別除了其直接職責外,還控制其副本數量。

  2. 常規類別或方法對單例的依賴在類別的公共契約中不可見。

  3. 全域變數是不好的。單例最終會變成一個巨大的全域變數。

  4. 單例的存在通常降低了應用程式的可測試性,特別是使用單例的類別的可測試性。

好吧,現在一切都結束了。我們研究了單例設計模式。現在,在與程式設計師朋友的終身對話中,您不僅可以說出它的優點,還可以說出它的缺點。祝你掌握新知識好運。

補充閱讀:

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION