本章主軸#

單例(Singleton)與雙重檢查鎖定(Double-Checked Locking)都是用來確保整個系統只有一個物件實例。差別在於前者用於單執行緒、後者用於多執行緒環境。

GoF 對 Singleton 的意圖描述#

確保一個類別只有一個實例,並提供存取它的全域點。

工作原理#

  • 一個特殊的 getInstance() 方法檢查實例是否已存在;不存在就建立
  • 把建構子設成 privateprotected,避免任何人繞過 getInstance() 直接 new
public class USTax extends Tax {
    private static USTax instance;
    private USTax() {}
    public static USTax getInstance() {
        if (instance == null) instance = new USTax();
        return instance;
    }
}

與多型一起使用#

在 e-commerce 案例中:

  • SalesOrder 想透過 Tax 抽象拿到唯一一個正確的 Tax 物件
  • 兩個觀念合在一起:
    • 隱藏哪個具體類別(用 Tax.getInstance() 內部選擇)
    • 隱藏實例數(用 Singleton 控制每個具體類別只有一個)

雙重檢查鎖定#

多執行緒下 Singleton 為何不夠#

兩個執行緒同時呼叫 getInstance(),皆檢查到 instance == null,雙雙進入 new。可能:

  • 浪費資源
  • 若 Singleton 有狀態,導致狀態不一致
  • 若是連線、計數器等資源 → 嚴重錯誤
  • C++ 中可能 memory leak

解法:兩段式檢查 + sync#

public static Tax getInstance() {
    if (instance == null) {
        synchronized (USTax.class) {
            if (instance == null) instance = new USTax();
        }
    }
    return instance;
}
  • 同步只在第一次必要時發生
  • 後續呼叫無鎖定開銷

這個寫法在早期 Java 中不可靠:JIT 可能在物件未完全初始化前先回傳參考。

C# 可用 volatile 解決;Java 採「static inner class holder」最簡單可靠:

public class USTax extends Tax {
    private static class Instance {
        static final Tax instance = new USTax();
    }
    public static Tax getInstance() { return Instance.instance; }
}

模式在不同語言要用不同手法落實。當概念與規格層次相同、實作層次有差異時——這正是模式的價值:讓你用共同語彙去談「同一個問題的不同實作」

Singleton 的關鍵特性#

欄位內容
Intent確保只有一個實例,且大家都用同一個,不必到處傳參考
Problem多個 Client 需用同一個物件,且不能多重實例化
Solution內建唯一 getter;建構子私有
ConsequencesClient 不必管實例化細節
Implementationprivate static 成員 + public static getter + private 建構子

Figure 21-1: Singleton 模式的通用結構——靜態 instance 與 getInstance 方法

實務筆記#

  • 不必要時別用:能直接用 static 成員的場合,沒必要 Singleton
  • 盡量無狀態:多執行緒環境中可能要 sync,否則簡化為無狀態最安全
  • 有狀態 Singleton ≈ 全域變數:易耦合無關模組
  • Facade、Strategy、Bridge 的具象類別若無狀態,常用 Singleton 實例化
  • 可擴展為 multi-instance:當需要少量固定數時,會逐步演化為 Object Pool

本章重點#

  • Singleton 是把「實例數限制」這項規則封進物件本身
  • 多執行緒下需要 Double-Checked Locking 或更安全的 holder 模式
  • Singleton 的概念在不同語言實作不同,模式提供了共同語彙讓討論可進行
  • 慎用有狀態的 Singleton,等同引入全域狀態