本章主軸#
單例(Singleton)與雙重檢查鎖定(Double-Checked Locking)都是用來確保整個系統只有一個物件實例。差別在於前者用於單執行緒、後者用於多執行緒環境。
GoF 對 Singleton 的意圖描述#
確保一個類別只有一個實例,並提供存取它的全域點。
工作原理#
- 一個特殊的
getInstance()方法檢查實例是否已存在;不存在就建立 - 把建構子設成
private或protected,避免任何人繞過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;建構子私有 |
| Consequences | Client 不必管實例化細節 |
| Implementation | private 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,等同引入全域狀態