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

Java 單例模式深度解析:從懶漢到雙重檢查鎖與餓漢式優化

     編輯:Java 相關知識

單例模式是最基礎也最常被面試考察的設計模式。本文透過一個「日誌工具類」的演進故事,層層剖析單例實作的坑與優化:從最基本的懶漢式、線程安全問題、同步鎖效能瓶頸、雙重檢查鎖(DCL)的正確寫法,到最終推薦的餓漢式與靜態內部類寫法,助你徹底掌握生產級單例寫法。

一、需求背景:為何需要單例?

專案中需要一個可控制列印等級的日誌工具類 LogUtil。若每次列印都 new LogUtil(),不僅浪費記憶體,且無法共享 level 設定。Leader 要求改為單例模式,確保 JVM 中僅存在一個實例。

二、演進歷程與關鍵知識點

1. 基礎懶漢式(非線程安全)

public class LogUtil {
    private static LogUtil instance;
    private LogUtil() {}
    public static LogUtil getInstance() {
        if (instance == null) {
            instance = new LogUtil(); // 多線程下可能創建多個實例
        }
        return instance;
    }
}

缺陷: 多線程並發呼叫 getInstance() 時,可能進入 if 區塊多次,產生多個實例。

2. 同步方法鎖(線程安全但效能差)

public synchronized static LogUtil getInstance() {
    if (instance == null) instance = new LogUtil();
    return instance;
}

缺陷: 每次呼叫都需獲取鎖,即使實例已建立,嚴重影響高併發效能。

3. 雙重檢查鎖(DCL)—— 經典優化

public static LogUtil getInstance() {
    if (instance == null) {                     // 第一次檢查,無鎖讀取
        synchronized (LogUtil.class) {          // 同步塊
            if (instance == null) {             // 第二次檢查,防止重複創建
                instance = new LogUtil();
            }
        }
    }
    return instance;
}

關鍵細節:

  • instance 必須宣告為 volatile,防止指令重排導致其他線程看到「半初始化」的物件。
  • 雙重 if 確保只有首次建立時進入同步塊,後續呼叫直接返回,效能極佳。

4. 靜態內部類(推薦:線程安全、延遲載入、程式碼簡潔)

public class LogUtil {
    private LogUtil() {}
    private static class Holder {
        static final LogUtil INSTANCE = new LogUtil();
    }
    public static LogUtil getInstance() {
        return Holder.INSTANCE;
    }
}

原理: 利用類別載入機制保證線程安全;Holder 類別只有在首次呼叫 getInstance() 時才會被載入初始化,實現延遲載入。

5. 餓漢式(最簡單,類別載入即建立實例)

public class LogUtil {
    private static final LogUtil INSTANCE = new LogUtil();
    private LogUtil() {}
    public static LogUtil getInstance() { return INSTANCE; }
}

優點: 無線程安全問題,程式碼最簡潔。缺點: 無延遲載入,若實例建構耗時且未被使用會浪費資源。

三、完整生產級 LogUtil 範例(採用靜態內部類)

public final class LogUtil {
    public static final int DEBUG = 0, INFO = 1, ERROR = 2, NOTHING = 3;
    private int level = DEBUG;

    private LogUtil() {}

    private static class Holder {
        static final LogUtil INSTANCE = new LogUtil();
    }

    public static LogUtil getInstance() {
        return Holder.INSTANCE;
    }

    public void debug(String msg) { if (DEBUG >= level) System.out.println("[DEBUG] " + msg); }
    public void info(String msg)  { if (INFO  >= level) System.out.println("[INFO]  " + msg); }
    public void error(String msg) { if (ERROR >= level) System.out.println("[ERROR] " + msg); }

    // 使用範例
    public static void main(String[] args) {
        LogUtil.getInstance().debug("Hello Singleton!");
    }
}

四、總結與選型建議

寫法線程安全延遲載入效能推薦場景
懶漢式(無鎖)❌✅高單線程或教學示範
同步方法✅✅低不推薦
DCL (+volatile)✅✅高需手動控制初始化時機
靜態內部類✅✅高最推薦(通用)
餓漢式✅❌最高實例輕量、必定會用到
Enum 單例✅❌高防反序列化破壞、防反射攻擊

實務上,**靜態內部類**與**Enum**是最穩健的選擇;若確定實例極輕量且必用,餓漢式最簡單直接。

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