单例模式(Singleton Pattern)是一种常用的软件设计模式,用于确保一个类仅有一个实例,并提供一个全局访问点来获取这个实例。在C#中,单例模式非常有用,特别是在需要管理共享资源访问(如数据库连接、配置文件读取器等)时。
一、单例模式介绍
实现单例模式的几个关键点:
- 私有构造函数:防止外部代码通过
new
关键字创建类的实例。 - 私有静态变量:用于存储类的唯一实例。
- 公共静态方法:提供一个全局访问点来获取类的唯一实例,如果实例不存在则创建它。
线程安全的单例模式实现
在多线程环境下,单例模式的实现需要确保线程安全,以防止多个线程同时创建实例。
懒汉式(线程不安全)
public class Singleton
{
private static Singleton instance;
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
注意:上述实现在多线程环境下是不安全的,因为两个线程可能同时进入if (instance == null)
判断,导致创建多个实例。
懒汉式(线程安全,双重检查锁定)
public class Singleton
{
private static volatile Singleton instance;
private static readonly object lockObject = new object();
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
注意:使用volatile
关键字确保instance
变量的读写操作不会被编译器优化,同时使用双重检查锁定(Double-Checked Locking)模式来减少锁的使用,提高效率。
饿汉式
public class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() {}
public static Singleton Instance
{
get
{
return instance;
}
}
}
注意:饿汉式在类加载时就完成了实例的初始化,因此是线程安全的,但它不是懒加载的,可能会浪费资源。
单例模式是一种非常有用的设计模式,但在实现时需要注意线程安全和资源利用的问题。在C#中,可以通过私有构造函数、私有静态变量和公共静态方法来实现单例模式,并根据具体需求选择懒汉式或饿汉式实现方式。
二、例子代码
用双重检查锁定的单例模式示例代码,同时展示如何在两个不同的线程中调用这个单例,一个线程用于不断写入数据,另一个线程用于不断读取并展示数据。
using System;
using System.Threading;
public class Singleton
{
// 私有静态变量,volatile确保在多个线程中读取时不会被缓存
private static volatile Singleton _instance;
// 私有构造函数
private Singleton()
{
}
// 公开的单例访问点
public static Singleton Instance
{
get
{
if (_instance == null) // 第一次检查
{
lock (typeof(Singleton)) // 锁定
{
if (_instance == null) // 第二次检查
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
// 简单的数据成员,用于演示写入和读取
public int Counter { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 第一个线程:不断写入数据
Thread writeThread = new Thread(() =>
{
while (true)
{
Singleton singleton = Singleton.Instance;
singleton.Counter++;
// 为了看到效果,可以添加一些输出或延迟
Console.WriteLine($"Write Thread: Counter = {singleton.Counter}");
Thread.Sleep(100); // 简单的延迟
}
});
// 第二个线程:不断读取并展示数据
Thread readThread = new Thread(() =>
{
while (true)
{
Singleton singleton = Singleton.Instance;
// 读取并展示数据
Console.WriteLine($"Read Thread: Counter = {singleton.Counter}");
Thread.Sleep(200); // 稍微长一点的延迟,以便看到写入和读取的差异
}
});
// 启动两个线程
writeThread.Start();
readThread.Start();
// 注意:这里直接退出了主线程,但两个子线程会继续运行
// 在实际应用中,你可能需要一种方式来优雅地停止这些线程
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}