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

C# 設計模式精講:單例模式

     編輯:關於C#

前言

單例模式是最基礎也最常被誤用的設計模式。本文透過遊戲伺服器負載平衡的真實場景,展示如何正確實作線程安全的單例,並探討其適用邊界。

模式意圖

確保一個類別只有一個實例存在,並提供一個全域存取點獲取該實例。

應用場景:遊戲伺服器負載平衡入口

開發網路遊戲伺服器端時,需承載大量玩家連線。類似 Web 負載平衡器,需要一個統一入口將玩家分發到不同的大廳伺服器。即使在高併發多線程情況下,這個入口必須是唯一的,否則會導致分發邏輯混亂、玩家重複分配等嚴重問題。

核心程式碼實作(雙重檢查鎖定)

using System;
using System.Collections.Generic;
using System.Threading;

namespace SingletonExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ParameterizedThreadStart ts = new ParameterizedThreadStart(EnterPlayer);
            for (int i = 0; i < 20; i++)
            {
                Thread t = new Thread(ts);
                t.Start("player" + i);
            }
            LoadBalanceServer.GetLoadBalanceServer().ShowServerInfo();
        }

        static void EnterPlayer(object playerName)
        {
            LoadBalanceServer lbs = LoadBalanceServer.GetLoadBalanceServer();
            lbs.GetLobbyServer().EnterPlayer(playerName.ToString());
        }
    }

    class LoadBalanceServer
    {
        private const int SERVER_COUNT = 3;
        private List<LobbyServer> serverList = new List<LobbyServer>();
        private static volatile LoadBalanceServer lbs;
        private static readonly object syncLock = new object();

        private LoadBalanceServer()
        {
            for (int i = 0; i < SERVER_COUNT; i++)
                serverList.Add(new LobbyServer("LobbyServer" + i));
        }

        public static LoadBalanceServer GetLoadBalanceServer()
        {
            if (lbs == null)
            {
                lock (syncLock)
                {
                    if (lbs == null)
                    {
                        Thread.Sleep(100); // 模擬高併發延遲,正式碼移除
                        lbs = new LoadBalanceServer();
                    }
                }
            }
            return lbs;
        }

        public LobbyServer GetLobbyServer()
        {
            LobbyServer ls = serverList[0];
            for (int i = 1; i < SERVER_COUNT; i++)
                if (serverList[i].PlayerList.Count < ls.PlayerList.Count)
                    ls = serverList[i];
            return ls;
        }

        public void ShowServerInfo()
        {
            foreach (LobbyServer ls in serverList)
            {
                Console.WriteLine("=================" + ls.ServerName + "=================");
                foreach (string player in ls.PlayerList)
                    Console.WriteLine(player);
            }
        }
    }

    class LobbyServer
    {
        private List<string> playerList = new List<string>();
        public List<string> PlayerList => playerList;
        public string ServerName { get; }
        public LobbyServer(string serverName) => this.ServerName = serverName;
        public void EnterPlayer(string playerName) => playerList.Add(playerName);
    }
}

程式碼執行結果示意

執行結果:20 個玩家被平均分發到 3 台大廳伺服器

關鍵實作細節解析

  • volatile 關鍵字:防止指令重排序導致其他線程看到半初始化的實例。
  • 雙重檢查鎖定:第一次 null 檢查避免不必要的鎖開銷;進入鎖後二次檢查確保單一實例化。
  • private 建構函式:阻止外部直接 new 實例。
  • 靜態工廠方法:GetLoadBalanceServer() 為唯一全域存取點。
  • Thread.Sleep(100):僅為演示高併發下的線程安全性,生產環境必須移除。

C# 更簡潔的替代方案:靜態初始化

public sealed class LoadBalanceServer
{
    private static readonly LoadBalanceServer instance = new LoadBalanceServer();
    static LoadBalanceServer() { } // 確保 lazy initialization
    private LoadBalanceServer() { /* 初始化邏輯 */ }
    public static LoadBalanceServer Instance => instance;
    // ... 其他方法
}

利用 CLR 保證的靜態建構函式線程安全與延遲初始化特性,程式碼更簡潔、效能更好,推薦作為首選。

何時採用單例模式

程式碼視角

  • 類別在系統中只能有一個實例(如配置管理器、連線池、日誌記錄器)。

應用視角

  • 需要一個「總管」協調資源分配,且分配邏輯不能有多個決策者(如流水號產生器、全域快取、硬體裝置驅動)。
  • 該物件狀態需在全系統共享且一致。

⚠️ 關鍵注意事項:不要濫用!

  • 全域狀態副作用:單例引入全域狀態,增加單元測試困難度、隱藏依賴關係、降低模組獨立性。
  • 擴展性受限:若未來需要多實例(如多租戶、多資料庫連線),重構成本極高。
  • 違反單一職責:類別同時負責業務邏輯與生命週期管理。
  • 只在「非單一實例不可」時使用:能用依賴注入管理生命週期時,優先選擇 DI 容器的單例註冊。

總結

單例模式核心在於「控制實例化過程」與「提供全域存取」。現代 C# 開發中,靜態初始化或 Lazy<T> 是更優雅的實作方式。但更重要的,是審視設計:這個類別真的只能有一個實例嗎?若答案不確定,請避免使用單例,改用依賴注入讓容器管理生命週期。

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