线程安全的单例
本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 52 课,共 70 课。
单例模式可确保一个类在整个应用程序中只有一个实例,并提供全局访问该实例的入口。这适用于管理共享资源,例如配置设置、日志服务或数据库连接。
基础的单例使用私有构造函数来阻止外部实例化,并使用静态属性提供访问入口:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // 私有构造函数
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}lock 语句可确保线程安全。如果多个线程尝试同时访问 Instance,同一时间只能有一个线程进入临界区。如果没有这个语句,两个线程都可能看到 _instance 为 null,并创建各自的实例。
C# 提供了一种更简单的线程安全方法,使用静态初始化:
public class Logger
{
private static readonly Logger _instance = new Logger();
private Logger() { }
public static Logger Instance => _instance;
public void Log(string message) => Console.WriteLine(message);
}这个版本是线程安全的,因为 CLR 保证静态字段只会初始化一次。这两种方法都能确保你始终获取同一个实例:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");挑战
简单让我们使用单例模式构建一个线程安全的配置管理器。在实际应用中,配置设置需要在整个程序中都能访问,同时还要确保只存在一个实例。这正是单例模式的理想使用场景。
你需要将代码分成两个文件:
ConfigManager.cs:在Configuration命名空间中创建一个实现单例模式的ConfigManager类。配置管理器应满足以下要求:- 有一个私有静态字段,用来保存唯一实例
- 使用私有构造函数,防止外部创建实例
- 提供一个静态
Instance属性,用来返回唯一实例(使用更简单的静态初始化方式来确保线程安全) - 包含一个私有字符串字段
_appName,并将其初始化为"MyApp" - 提供一个
SetAppName(string name)方法,用来更新应用名称 - 提供一个
GetAppName()方法,用来返回当前应用名称
Program.cs:在主文件中,多次访问该实例并展示更改会在所有引用中保留,以证明单例模式能够正常工作。
你会收到一个输入:
- 要设置的新应用名称(例如,
ProductionServer)
在程序中:
- 获取
ConfigManager实例,并打印默认应用名称 - 再次获取该实例(存入另一个变量),并使用它将输入的新应用名称设为应用名称
- 第三次获取该实例,并打印应用名称,以证明所有引用共享相同的数据
例如,如果输入是 ProductionServer,输出应为:
MyApp
ProductionServer这展示了单例模式的核心优势:无论你访问 Instance 多少次,得到的始终是同一个对象,并且共享同一状态。静态初始化方式能够确保线程安全,因为即使多个线程同时访问,CLR 也会确保静态字段只初始化一次。
亲手试试
using System;
using Configuration;
class Program
{
public static void Main(string[] args)
{
// 读取输入的新应用名称
string newAppName = Console.ReadLine();
// TODO:获取 ConfigManager 实例并打印默认应用名称
// TODO:再次获取实例(存入另一个变量),用它设置新的应用名称
// TODO:第三次获取实例并打印应用名称,证明所有引用共享同一份数据
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器