首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

深入解析 Java 單例模式:不完全單例、預設實例與雙重檢查鎖定的陷阱

     編輯:Java 相關知識

什麼是不完全的單例類?

在學習單例模式時,你可能會遇到一種「不完全」的單例類。乍看之下,它似乎符合單例模式的結構:擁有靜態工廠方法 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;
    }
}

造成不完全單例的常見原因

  1. 初學者疏忽:未意識到單例的建構子必須是私有的。修正方法簡單:將建構子改為 private 即可。
  2. 需求變更導致的妥協:原本設計為單例,後來發現需要多個實例,乾脆把建構子改為公開。這種情況應考慮重構為「多例模式」而非破壞單例結構。
  3. 刻意設計的「預設實例模式」:設計者同時提供靜態工廠方法與公開建構子,讓呼叫端自行選擇使用共享實例或建立新實例。這看似靈活,實則將設計責任推給呼叫端,容易導致濫用與不一致,不建議採用。

相關模式延伸

多例模式

單例精神可推廣至「有限多個實例」的場景,稱為多例模式。例如系統需針對不同語系提供各自的資源管理器,便可用多例模式管理。其類別圖與單例類似,但登錄表持有多個鍵值對應的實例。

單例與多例類別圖對比

簡單工廠模式

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

簡單工廠模式類別圖

附錄:雙重檢查鎖定在 Java 中為何失效?

雙重檢查鎖定試圖在「懶漢式」單例中減少同步開銷:先檢查實例是否為 null,再進入同步區塊二次檢查後才建立實例。理論上似乎完美,但在 Java 語言中無法正確運作。

失效根本原因

Java 記憶體模型允許編譯器與處理器重新排序指令。m_instance = new LazySingleton() 並非原子操作,實際包含:分配記憶體、呼叫建構子初始化物件、將參考指派給 m_instance。重新排序可能導致參考指派在物件初始化完成前就被其他執行緒看見,造成該執行緒拿到一個「半初始化」的物件,進而拋出例外或產生邏輯錯誤。

經典文獻指出:雙重檢查鎖定在 Java 中根本不可行。雖然 JDK 5 透過 volatile 關鍵字修正了記憶體可見性問題,但寫法仍嫌複雜且易出錯。

最佳實務建議

  • 優先使用餓漢式單例(類別載入時靜態初始化),簡單、執行緒安全、效能佳。
  • 若必須延遲初始化,採用靜態內部類或枚舉實現,利用類別載入機制天然保證執行緒安全。
  • 若堅持使用同步方法,直接在 getInstance() 上加 synchronized,雖有微小效能代價,但程式碼清晰、可維護。

切勿在生產環境嘗試手寫雙重檢查鎖定,除非你極度熟悉 Java Memory Model 並願意承擔風險。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved