====== 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 (推荐) ====
在 .NET 4.0 及更高版本中,这是**最推荐**的实现方式。它利用 `System.Lazy` 类型自动处理线程安全和延迟加载,代码简洁且高效。
public sealed class Singleton
{
// Lazy 默认是线程安全的
private static readonly Lazy _lazy =
new Lazy(() => new Singleton());
private Singleton() { }
public static Singleton Instance
{
get
{
return _lazy.Value;
}
}
}
===== 4. 总结 =====
^ 实现方式 ^ 线程安全 ^ 延迟加载 ^ 复杂度 ^ 推荐指数 ^
| 简单实现 | No | Yes | 低 | 不推荐 |
| 简单加锁 | Yes | Yes | 中 | 一般 |
| 双重检查锁定 | Yes | Yes | 高 | 一般 |
| 静态初始化 | Yes | No | 低 | 推荐 (如果不需要延迟加载) |
| **System.Lazy** | **Yes** | **Yes** | **低** | **强烈推荐** |
===== 5. 注意事项 =====
- **单一职责原则**:单例类除了管理自己的实例外,通常还承担具体的业务功能。要注意不要让单例类变得过于臃肿。
- **测试困难**:单例模式通常持有全局状态,这使得单元测试变得困难(难以模拟和重置状态)。在需要高度可测试性的系统中,通常建议使用**依赖注入 (Dependency Injection)** 容器来管理单例的生命周期,而不是手动实现单例模式。