单例模式详解
大约 9 分钟约 2815 字
单例模式详解
简介
单例模式(Singleton Pattern)是最简单也是最常用的创建型设计模式之一。它确保一个类只有一个实例,并提供全局访问点。在 C# 中,单例模式的实现方式多种多样,从简单的静态初始化到线程安全的 Lazy<T>,再到依赖注入框架中的单例生命周期,每种方式都有其适用场景和注意事项。
特点
基础实现方式
非线程安全实现
// 最简单的单例实现 - 非线程安全
public sealed class SimpleSingleton
{
private static SimpleSingleton? _instance;
// 私有构造函数防止外部实例化
private SimpleSingleton()
{
Console.WriteLine("SimpleSingleton 实例被创建");
}
public static SimpleSingleton Instance
{
get
{
if (_instance == null)
{
_instance = new SimpleSingleton();
}
return _instance;
}
}
public void DoSomething()
{
Console.WriteLine("单例方法执行");
}
}线程安全实现
// 双重检查锁定(Double-Check Locking)
public sealed class ThreadSafeSingleton
{
private static ThreadSafeSingleton? _instance;
private static readonly object _lock = new();
private ThreadSafeSingleton() { }
public static ThreadSafeSingleton Instance
{
get
{
if (_instance == null) // 第一次检查(无锁)
{
lock (_lock)
{
if (_instance == null) // 第二次检查(有锁)
{
_instance = new ThreadSafeSingleton();
}
}
}
return _instance;
}
}
public DateTime CreatedAt { get; } = DateTime.UtcNow;
}使用 Lazy<T> 实现
// 推荐:使用 Lazy<T> 实现线程安全的延迟初始化
public sealed class LazySingleton
{
// Lazy<T> 默认是线程安全的(LazyThreadSafetyMode.ExecutionAndPublication)
private static readonly Lazy<LazySingleton> _instance =
new(() => new LazySingleton());
private LazySingleton()
{
// 初始化操作
Console.WriteLine("LazySingleton 在首次访问时创建");
}
public static LazySingleton Instance => _instance.Value;
// 单例可以持有状态
public Dictionary<string, object> Cache { get; } = new();
public void Set(string key, object value) => Cache[key] = value;
public T? Get<T>(string key) =>
Cache.TryGetValue(key, out var value) ? (T)value : default;
}静态初始化实现
// 静态初始化 - .NET 运行时保证线程安全
public sealed class StaticSingleton
{
// 静态初始化在首次访问类的任何成员时触发
// .NET CLR 保证静态构造函数只执行一次且线程安全
public static readonly StaticSingleton Instance = new();
// 显式静态构造函数告诉 C# 编译器不要标记 beforefieldinit
static StaticSingleton() { }
private StaticSingleton()
{
// 耗时的初始化操作
Thread.Sleep(100);
}
public string AppName { get; set; } = "My Application";
public Version Version { get; set; } = new(1, 0, 0);
}依赖注入中的单例
在 ASP.NET Core 中,推荐使用 DI 容器管理单例生命周期,而非手动实现单例模式。
// 定义单例服务
public interface IConfigurationCache
{
T? Get<T>(string key);
void Set<T>(string key, T value, TimeSpan? expiration = null);
void Remove(string key);
}
public class MemoryConfigurationCache : IConfigurationCache
{
private readonly ConcurrentDictionary<string, CacheEntry> _cache = new();
private readonly Timer _cleanupTimer;
public MemoryConfigurationCache()
{
// 每分钟清理过期缓存
_cleanupTimer = new Timer(_ => CleanupExpired(), null,
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
public T? Get<T>(string key)
{
if (_cache.TryGetValue(key, out var entry) && !entry.IsExpired)
return (T)entry.Value;
_cache.TryRemove(key, out _);
return default;
}
public void Set<T>(string key, T value, TimeSpan? expiration = null)
{
_cache[key] = new CacheEntry(value, expiration);
}
public void Remove(string key) => _cache.TryRemove(key, out _);
private void CleanupExpired()
{
foreach (var kvp in _cache.Where(k => k.Value.IsExpired))
_cache.TryRemove(kvp.Key, out _);
}
private record CacheEntry(object Value, TimeSpan? Expiration)
{
public DateTime CreatedAt { get; } = DateTime.UtcNow;
public bool IsExpired => Expiration.HasValue &&
DateTime.UtcNow - CreatedAt > Expiration.Value;
}
}在 DI 容器中注册和使用
// Program.cs - 注册为单例
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IConfigurationCache, MemoryConfigurationCache>();
builder.Services.AddSingleton<IConnectionFactory>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return new SqlConnectionFactory(config.GetConnectionString("Default")!);
});
// 在控制器中使用
[ApiController]
[Route("api/[controller]")]
public class ConfigController : ControllerBase
{
private readonly IConfigurationCache _cache;
public ConfigController(IConfigurationCache cache)
{
_cache = cache;
}
[HttpGet("{key}")]
public IActionResult Get(string key)
{
var value = _cache.Get<string>(key);
return value != null ? Ok(new { key, value }) : NotFound();
}
[HttpPut("{key}")]
public IActionResult Set(string key, [FromBody] string value)
{
_cache.Set(key, value, TimeSpan.FromHours(1));
return Ok();
}
}单例的常见陷阱与解决方案
陷阱一:多线程竞态条件
// 问题代码:非线程安全的懒加载
public sealed class BadSingleton
{
private static BadSingleton? _instance;
private BadSingleton() { }
public static BadSingleton Instance
{
get
{
// 两个线程同时判断 _instance == null,都会进入创建分支
if (_instance == null)
{
_instance = new BadSingleton(); // 创建了两个实例!
}
return _instance;
}
}
}
// 解决方案1:双重检查锁定
public sealed class DclSingleton
{
private static DclSingleton? _instance;
private static readonly object _lock = new();
private DclSingleton() { }
public static DclSingleton Instance
{
get
{
if (_instance == null) // 第一次检查(无锁,快速路径)
{
lock (_lock) // 加锁
{
if (_instance == null) // 第二次检查(有锁,确保唯一)
{
_instance = new DclSingleton();
}
}
}
return _instance;
}
}
}
// 解决方案2:Lazy<T>(推荐)
public sealed class SafeSingleton
{
private static readonly Lazy<SafeSingleton> _lazy =
new(() => new SafeSingleton(), LazyThreadSafetyMode.ExecutionAndPublication);
public static SafeSingleton Instance => _lazy.Value;
private SafeSingleton() { }
}陷阱二:序列化破坏单例
// 问题:反序列化时会创建新实例,破坏单例
[Serializable]
public sealed class SerializableSingleton_Bad
{
public static readonly SerializableSingleton_Bad Instance = new();
private SerializableSingleton_Bad() { }
}
// 即使 Instance 是单例,反序列化后会得到新对象
// var copy = (SerializableSingleton_Bad)formatter.Deserialize(stream);
// copy != Instance — 单例被破坏
// 解决方案:实现 ISerializable 并在反序列化时返回现有实例
[Serializable]
public sealed class SerializableSingleton : ISerializable
{
public static readonly SerializableSingleton Instance = new();
private SerializableSingleton() { }
protected SerializableSingleton(SerializationInfo info, StreamingContext context)
{
// 反序列化时不做任何事,直接返回 Instance
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// 不序列化任何字段
}
}陷阱三:依赖注入中的生命周期陷阱
// 常见错误:在 Scoped 服务中注入 Singleton
public class ScopedService
{
private readonly SingletonService _singleton;
public ScopedService(SingletonService singleton)
{
_singleton = singleton; // Singleton 服务被注入到 Scoped 中
}
}
// 如果 SingletonService 依赖了 Scoped 服务(如 DbContext)
// 会导致 DbContext 被 Singleton 长期持有,无法释放 — 内存泄漏
public class SingletonService
{
private readonly AppDbContext _dbContext; // 危险!DbContext 是 Scoped
public SingletonService(AppDbContext dbContext)
{
_dbContext = dbContext; // DbContext 永远不会被释放
}
}
// 解决方案:使用 IServiceProvider 或 IServiceScopeFactory 延迟获取 Scoped 服务
public class SafeSingletonService
{
private readonly IServiceScopeFactory _scopeFactory;
public SafeSingletonService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void DoWork()
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 使用完自动释放
}
}单例模式变体
带参数的单例
// 需要参数的单例 — 使用工厂模式
public interface IConfigProvider
{
string Get(string key);
}
public class ConfigProvider : IConfigProvider
{
private readonly string _configPath;
private readonly Dictionary<string, string> _config;
private ConfigProvider(string configPath)
{
_configPath = configPath;
_config = File.ReadAllLines(configPath)
.Where(line => line.Contains('='))
.Select(line => line.Split('=', 2))
.ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
}
public string Get(string key) =>
_config.TryGetValue(key, out var value) ? value : string.Empty;
// 工厂管理单例实例
public static class Factory
{
private static readonly ConcurrentDictionary<string, ConfigProvider> _instances = new();
public static ConfigProvider GetInstance(string configPath)
{
return _instances.GetOrAdd(configPath, path => new ConfigProvider(path));
}
}
}
// 使用 — 不同配置文件得到不同单例
var appConfig = ConfigProvider.Factory.GetInstance("appsettings.json");
var dbConfig = ConfigProvider.Factory.GetInstance("dbsettings.json");泛型单例
// 泛型单例管理器 — 适用于需要多种单例类型的场景
public static class Singleton<T> where T : class, new()
{
private static readonly Lazy<T> _instance = new(() => new T());
public static T Instance => _instance.Value;
}
// 使用
public class CacheManager
{
public Dictionary<string, object> Cache { get; } = new();
}
public class Logger
{
public void Log(string message) => Console.WriteLine(message);
}
var cache = Singleton<CacheManager>.Instance;
var logger = Singleton<Logger>.Instance;线程安全的单例事件聚合器
// 单例 + 事件 — 实现进程内的事件总线
public sealed class EventAggregator
{
private static readonly Lazy<EventAggregator> _instance =
new(() => new EventAggregator());
public static EventAggregator Instance => _instance.Value;
private readonly ConcurrentDictionary<Type, List<Delegate>> _handlers = new();
private EventAggregator() { }
public void Subscribe<T>(Action<T> handler)
{
var handlers = _handlers.GetOrAdd(typeof(T), _ => new List<Delegate>());
lock (handlers)
{
handlers.Add(handler);
}
}
public void Publish<T>(T payload)
{
if (!_handlers.TryGetValue(typeof(T), out var handlers)) return;
lock (handlers)
{
foreach (var handler in handlers.ToArray()) // ToArray 防止迭代时修改
{
((Action<T>)handler)(payload);
}
}
}
public void Unsubscribe<T>(Action<T> handler)
{
if (_handlers.TryGetValue(typeof(T), out var handlers))
{
lock (handlers)
{
handlers.Remove(handler);
}
}
}
}单元测试中的单例处理
// 可测试的单例设计:通过接口抽象
public interface IAppContext
{
string CurrentUser { get; }
DateTime Now { get; }
string Environment { get; }
}
// 生产环境的单例实现
public class ProductionAppContext : IAppContext
{
public string CurrentUser =>
Thread.CurrentPrincipal?.Identity?.Name ?? "Anonymous";
public DateTime Now => DateTime.UtcNow;
public string Environment => "Production";
}
// 测试环境可以使用不同的实现
public class TestAppContext : IAppContext
{
public string CurrentUser { get; set; } = "TestUser";
public DateTime Now { get; set; } = new(2024, 1, 1);
public string Environment { get; set; } = "Test";
}
// 测试时替换
[TestClass]
public class OrderServiceTests
{
[TestMethod]
public void ProcessOrder_WithTestContext_WorksCorrectly()
{
var context = new TestAppContext { CurrentUser = "TestUser" };
var service = new OrderService(context);
var result = service.ProcessOrder(new Order());
Assert.IsTrue(result.Success);
}
}优点
缺点
总结
单例模式在 C# 中有多种实现方式,推荐使用 Lazy<T> 或静态初始化来保证线程安全。在 ASP.NET Core 项目中,更推荐通过依赖注入容器(AddSingleton)来管理单例生命周期,这样可以获得更好的可测试性和可维护性。无论选择哪种方式,都需要注意线程安全、内存管理和单元测试的可行性。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来“高级”而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《单例模式详解》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕“为了模式而模式”,尤其是在简单业务里。
复盘问题
- 如果把《单例模式详解》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《单例模式详解》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《单例模式详解》最大的收益和代价分别是什么?
