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

.NET 設計模式(2):單例模式——確保全域唯一實例的五種實現藝術

     編輯:關於.NET

前言:類別設計者的責任,而非使用者的負擔

單例模式(Singleton Pattern)意圖明確:保證一個類別僅有一個實例,並提供一個全域存取點(GoF)。這不是「如何創建物件」,而是「如何限制物件數量」。在 .NET 中,根據執行緒安全性、延遲初始化需求、效能考量,有五種經典實現方式。

五種實現方式深度對比

1. 簡單版(非執行緒安全)


public sealed class Singleton {
    private static Singleton _instance = null;
    private Singleton() { }
    public static Singleton Instance {
        get {
            if (_instance == null) _instance = new Singleton();
            return _instance;
        }
    }
}

缺陷:多執行緒同時判斷 _instance == null 為真,導致創建多個實例。僅適用於單執行緒或啟動階段初始化場景。

2. 鎖同步版(執行緒安全、效能較差)


public sealed class Singleton {
    private static Singleton _instance = null;
    private static readonly object _lock = new object();
    private Singleton() { }
    public static Singleton Instance {
        get {
            lock (_lock) {
                if (_instance == null) _instance = new Singleton();
                return _instance;
            }
        }
    }
}

每次存取都加鎖,效能開銷大。雙重檢查鎖定(DCL)在 .NET 記憶體模型下需配合 volatile 或 Thread.MemoryBarrier,易出錯,不推薦手寫 DCL。

3. 靜態初始化版(推薦:簡單、執行緒安全、效能佳)


public sealed class Singleton {
    private static readonly Singleton _instance = new Singleton();
    static Singleton() { } // 顯式靜態建構子觸發延遲初始化(BeforeFieldInit 控制)
    private Singleton() { }
    public static Singleton Instance => _instance;
}

原理:CLR 保證靜態欄位初始化的執行緒安全性;readonly 確保唯一賦值;sealed 禁止繼承破壞單例。這是 .NET 中最推薦的標準實現。

4. 巢狀類別延遲初始化版(完全延遲、執行緒安全)


public sealed class Singleton {
    private Singleton() { }
    public static Singleton Instance => Nested._instance;
    private class Nested {
        static Nested() { }
        internal static readonly Singleton _instance = new Singleton();
    }
}

優勢:利用巢狀類別的靜態建構子特性,只有首次存取 Nested._instance 時才初始化。若單例建構耗時/耗資源且不一定被用到,此版本完美平衡「延遲」與「安全」。

5. .NET 4+ Lazy<T> 版(現代標準庫解法)


public sealed class Singleton {
    private static readonly Lazy<Singleton> _instance = 
        new Lazy<Singleton>(() => new Singleton(), LazyThreadSafetyMode.ExecutionAndPublication);
    private Singleton() { }
    public static Singleton Instance => _instance.Value;
}

優勢:框架級支援、語義清晰、可選執行緒安全模式、支援例外傳播與重試。現代 .NET 專案首選。

實戰範例:多執行緒計數器驗證


public sealed class CounterSingleton {
    private static readonly CounterSingleton _instance = new CounterSingleton();
    private int _count = 0;
    private CounterSingleton() { Thread.Sleep(2000); } // 模擬耗時初始化
    public static CounterSingleton Instance => _instance;
    public void Increment() => Interlocked.Increment(ref _count);
    public int Value => _count;
}

public class Worker {
    public void DoWork() {
        for (int i = 0; i < 4; i++) {
            var c = CounterSingleton.Instance;
            c.Increment();
            Console.WriteLine($"{Thread.CurrentThread.Name} -> Count: {c.Value}");
        }
    }
}

// 四個執行緒並發,最終計數必為 16,且只有一個實例被創建

單例模式的優缺點與適用場景

優點缺點
實例控制:嚴格保證唯一性開銷:每次存取屬性有微小檢查成本(靜態初始化版幾乎為零)
靈活性:類別掌控實例化過程開發困惑:使用者可能嘗試 new 失敗,需文檔說明
全域存取:方便跨模組共享狀態生命週期管理:無法主動銷毀(GC 回收前常駐),單元測試難隔離
延遲初始化:按需創建節省資源隱藏相依:類別間隱性耦合,違反顯式相依原則

適用場景

  • 系統只需一個實例:印表機佇列、視窗管理器、配置管理器、日誌記錄器
  • 實例創建昂貴/耗資源:資料庫連線池、大型快取、硬體裝置封裝
  • 需全域統一狀態:應用程式設定、授權上下文、ID 生成器
  • .NET Remoting 伺服器啟動模式 WellKnownObjectMode.Singleton

常見誤區與現代替代方案

  1. 濫用單例當全域變數:導致強耦合、難測試。替代:DI 容器管理單例生命週期(services.AddSingleton<T>()),建構子注入顯式相依
  2. 靜態類別 vs 單例:靜態類別無法實作介面、無法繼承、無法延遲初始化、無法作為參數傳遞。單例更靈活
  3. 雙重檢查鎖定 (DCL) 手寫:.NET 記憶體模型下極易出錯(指令重排)。禁止手寫,用靜態初始化或 Lazy<T>
  4. 單例與序列化:反序列化會創建新實例,需實作 ISerializable 或 IDeserializationCallback 回傳現有實例

.NET 生態內建單例

  • HttpContext.Current(舊版 ASP.NET)
  • ConfigurationManager.AppSettings
  • Trace.Listeners
  • TaskScheduler.Default
  • SynchronizationContext.Current

總結

單例模式看似簡單,實則蘊含「執行緒安全」、「延遲初始化」、「記憶體模型」、「生命週期管理」等深層議題。在 .NET 中,靜態初始化版與 Lazy<T> 版是兩大金標準,前者極簡高效,後者語義現代且功能豐富。但更重要的現代觀點是:單例應作為實現細節隱藏在 DI 容器之後,而非洩漏為公共靜態屬性。讓容器管理單例生命週期,類別專注業務邏輯,這才是 .NET Core/5+ 時代的正統之道。

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