C# 单例模式 (Singleton Pattern)
单例模式是 C# 开发中最常用的设计模式之一。它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。
1. 为什么使用单例模式?
在 C# 应用程序中,单例模式通常用于管理共享资源,例如:
- 数据库连接池
- 日志记录器 (Logger)
- 配置文件管理器 (Configuration Manager)
- 硬件接口访问(如打印机后台处理程序)
2. 实现单例模式的关键点
要实现一个标准的单例类,必须满足以下条件:
- 私有构造函数:防止外部通过 `new` 关键字创建实例。
- 私有静态变量:用于持有类的唯一实例。
- 公共静态方法/属性:提供全局访问点(通常命名为 `Instance`)。
3. C# 中的几种实现方式
在 C# 中实现单例有多种方式,主要区别在于线程安全性和延迟加载 (Lazy Loading) 的处理。
3.1. 简单的线程不安全实现 (Bad)
这是最基本的实现,但在多线程环境下是不安全的。如果两个线程同时判断 `_instance == null`,可能会创建两个实例。
public class Singleton { private static Singleton _instance; // 私有构造函数 private Singleton() { } public static Singleton Instance { get { // 线程不安全! if (_instance == null) { _instance = new Singleton(); } return _instance; } } }
3.2. 简单的线程安全实现 (加锁)
通过 `lock` 关键字确保同一时间只有一个线程执行创建实例的代码。
public class Singleton { private static Singleton _instance; private static readonly object _lock = new object(); private Singleton() { } public static Singleton Instance { get { // 每次访问都会加锁,性能较差 lock (_lock) { if (_instance == null) { _instance = new Singleton(); } return _instance; } } } }
3.3. 双重检查锁定 (Double-Check Locking)
这种方式既保证了线程安全,又避免了每次访问都加锁带来的性能损耗。只有在实例未创建时才加锁。
public class Singleton { private static Singleton _instance; private static readonly object _lock = new object(); private Singleton() { } public static Singleton Instance { get { // 第一次检查:如果已经创建,直接返回,避免加锁 if (_instance == null) { lock (_lock) { // 第二次检查:防止多个线程同时通过了第一次检查 if (_instance == null) { _instance = new Singleton(); } } } return _instance; } } }
3.4. 静态初始化 (饿汉式)
利用 .NET CLR 的特性,静态构造函数只会执行一次。这种方式非常简单且线程安全,但缺点是不支持延迟加载(类一加载,实例就被创建,即使你还没用到它)。
public sealed class Singleton { // CLR 保证了静态字段初始化的线程安全性 private static readonly Singleton _instance = new Singleton(); // 显式静态构造函数告诉 C# 编译器不要将类型标记为 BeforeFieldInit static Singleton() { } private Singleton() { } public static Singleton Instance { get { return _instance; } } }
3.5. 使用 System.Lazy<T> (推荐)
在 .NET 4.0 及更高版本中,这是最推荐的实现方式。它利用 `System.Lazy<T>` 类型自动处理线程安全和延迟加载,代码简洁且高效。
public sealed class Singleton { // Lazy<T> 默认是线程安全的 private static readonly Lazy<Singleton> _lazy = new Lazy<Singleton>(() => new Singleton()); private Singleton() { } public static Singleton Instance { get { return _lazy.Value; } } }
4. 总结
| 实现方式 | 线程安全 | 延迟加载 | 复杂度 | 推荐指数 |
|---|---|---|---|---|
| 简单实现 | No | Yes | 低 | 不推荐 |
| 简单加锁 | Yes | Yes | 中 | 一般 |
| 双重检查锁定 | Yes | Yes | 高 | 一般 |
| 静态初始化 | Yes | No | 低 | 推荐 (如果不需要延迟加载) |
| System.Lazy<T> | Yes | Yes | 低 | 强烈推荐 |
5. 注意事项
- 单一职责原则:单例类除了管理自己的实例外,通常还承担具体的业务功能。要注意不要让单例类变得过于臃肿。
- 测试困难:单例模式通常持有全局状态,这使得单元测试变得困难(难以模拟和重置状态)。在需要高度可测试性的系统中,通常建议使用依赖注入 (Dependency Injection) 容器来管理单例的生命周期,而不是手动实现单例模式。
评论