在學習單例模式時,你可能會遇到一種「不完全」的單例類。乍看之下,它似乎符合單例模式的結構:擁有靜態工廠方法 getInstance()、私有靜態實例變數,甚至還加了同步關鍵字。但仔細一看,類別提供了一個 公開的建構子。這意味著外部程式碼可以隨意呼叫 new LazySingleton() 建立任意數量的實例,徹底違背了單例「全系統僅一個實例」的核心約束。
以下程式碼示範了這種錯誤寫法:
package com.javapatterns.singleton.demos;
public class LazySingleton {
private static LazySingleton m_instance = null;
// 錯誤:公開建構子允許外部直接實例化
public LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (m_instance == null) {
m_instance = new LazySingleton();
}
return m_instance;
}
}
private 即可。單例精神可推廣至「有限多個實例」的場景,稱為多例模式。例如系統需針對不同語系提供各自的資源管理器,便可用多例模式管理。其類別圖與單例類似,但登錄表持有多個鍵值對應的實例。

單例的靜態工廠方法本質上屬於簡單工廠模式。例如 ConfigManager.getInstance()、Toolkit.getDefaultToolkit() 皆屬此類。

雙重檢查鎖定試圖在「懶漢式」單例中減少同步開銷:先檢查實例是否為 null,再進入同步區塊二次檢查後才建立實例。理論上似乎完美,但在 Java 語言中無法正確運作。
Java 記憶體模型允許編譯器與處理器重新排序指令。m_instance = new LazySingleton() 並非原子操作,實際包含:分配記憶體、呼叫建構子初始化物件、將參考指派給 m_instance。重新排序可能導致參考指派在物件初始化完成前就被其他執行緒看見,造成該執行緒拿到一個「半初始化」的物件,進而拋出例外或產生邏輯錯誤。
經典文獻指出:雙重檢查鎖定在 Java 中根本不可行。雖然 JDK 5 透過 volatile 關鍵字修正了記憶體可見性問題,但寫法仍嫌複雜且易出錯。
getInstance() 上加 synchronized,雖有微小效能代價,但程式碼清晰、可維護。切勿在生產環境嘗試手寫雙重檢查鎖定,除非你極度熟悉 Java Memory Model 並願意承擔風險。