单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取该实例。以下是详细介绍单例模式的各种实现方式。
1. 饿汉式单例模式
代码示例
using System;
// 饿汉式单例类
public sealed class EagerSingleton
{
// 静态只读字段,在类加载时就初始化实例
private static readonly EagerSingleton instance = new EagerSingleton();
// 私有构造函数,防止外部实例化
private EagerSingleton()
{
Console.WriteLine("EagerSingleton 实例已创建");
}
// 公共静态属性,用于获取单例实例
public static EagerSingleton Instance
{
get
{
return instance;
}
}
// 示例方法
public void DoSomething()
{
Console.WriteLine("EagerSingleton 正在执行操作");
}
}
class Program
{
static void Main()
{
// 获取单例实例
EagerSingleton singleton = EagerSingleton.Instance;
// 调用示例方法
singleton.DoSomething();
}
}
案例解读
- 类加载与实例创建 :在 C# 中,当一个类被加载时,其静态成员会被初始化。这里的
private static readonly EagerSingleton instance = new EagerSingleton();
使得EagerSingleton
类在加载时就创建了唯一的实例。readonly
关键字保证了这个实例一旦被创建,就不能被重新赋值。 - 私有构造函数 :
private EagerSingleton()
阻止了外部代码通过new
关键字创建新的EagerSingleton
实例,确保了单例的唯一性。 - 公共静态属性 :
public static EagerSingleton Instance
提供了一个全局访问点,外部代码可以通过EagerSingleton.Instance
来获取这个唯一的实例。
优缺点分析
- 优点:实现简单,线程安全。由于实例在类加载时就创建好了,不存在多线程同时创建实例的问题。
- 缺点:可能会造成资源浪费。如果这个单例实例在整个应用程序的生命周期中都没有被使用,那么它仍然会在类加载时被创建,占用系统资源。
2. 懒汉式单例模式(非线程安全)
代码示例
using System;
// 懒汉式单例类(非线程安全)
public sealed class LazySingletonNonThreadSafe
{
// 静态字段,初始为 null
private static LazySingletonNonThreadSafe instance;
// 私有构造函数,防止外部实例化
private LazySingletonNonThreadSafe()
{
Console.WriteLine("LazySingletonNonThreadSafe 实例已创建");
}
// 公共静态属性,用于获取单例实例
public static LazySingletonNonThreadSafe Instance
{
get
{
if (instance == null)
{
instance = new LazySingletonNonThreadSafe();
}
return instance;
}
}
// 示例方法
public void DoSomething()
{
Console.WriteLine("LazySingletonNonThreadSafe 正在执行操作");
}
}
class Program
{
static void Main()
{
// 获取单例实例
LazySingletonNonThreadSafe singleton = LazySingletonNonThreadSafe.Instance;
// 调用示例方法
singleton.DoSomething();
}
}
案例解读
- 延迟实例化 :
private static LazySingletonNonThreadSafe instance;
初始化为null
,在第一次调用Instance
属性时,通过if (instance == null)
判断实例是否已经创建,如果未创建则创建新实例。这种方式实现了延迟加载,只有在需要使用实例时才会创建。 - 非线程安全问题 :在多线程环境下,可能会有多个线程同时进入
if (instance == null)
这个判断语句,从而导致多个线程都创建了新的实例,破坏了单例的唯一性。
优缺点分析
- 优点:实现了延迟加载,避免了不必要的资源浪费。
- 缺点:非线程安全,在多线程环境下不能保证单例的唯一性。
3. 懒汉式单例模式(线程安全,简单加锁)
代码示例
using System;
// 懒汉式单例类(线程安全,简单加锁)
public sealed class LazySingletonThreadSafeSimple
{
// 静态字段,初始为 null
private static LazySingletonThreadSafeSimple instance;
// 静态对象,用于加锁
private static readonly object lockObject = new object();
// 私有构造函数,防止外部实例化
private LazySingletonThreadSafeSimple()
{
Console.WriteLine("LazySingletonThreadSafeSimple 实例已创建");
}
// 公共静态属性,用于获取单例实例
public static LazySingletonThreadSafeSimple Instance
{
get
{
lock (lockObject)
{
if (instance == null)
{
instance = new LazySingletonThreadSafeSimple();
}
}
return instance;
}
}
// 示例方法
public void DoSomething()
{
Console.WriteLine("LazySingletonThreadSafeSimple 正在执行操作");
}
}
class Program
{
static void Main()
{
// 获取单例实例
LazySingletonThreadSafeSimple singleton = LazySingletonThreadSafeSimple.Instance;
// 调用示例方法
singleton.DoSomething();
}
}
案例解读
- 加锁机制 :
private static readonly object lockObject = new object();
创建了一个静态的锁对象。在Instance
属性的get
方法中,使用lock (lockObject)
对代码块进行加锁,确保同一时间只有一个线程能进入这个代码块。 - 线程安全实现 :通过加锁,当一个线程进入
lock
代码块时,其他线程会被阻塞,直到该线程完成实例的创建。这样就保证了在多线程环境下实例只会被创建一次。
优缺点分析
- 优点:实现了线程安全,保证了单例的唯一性。
- 缺点:每次获取实例都需要加锁,加锁操作会带来一定的性能开销。即使实例已经创建好了,后续的线程访问仍然需要进行加锁操作,这在高并发场景下会影响性能。
4. 双重检查锁定单例模式(线程安全)
代码示例
using System;
// 双重检查锁定单例类
public sealed class DoubleCheckedLockingSingleton
{
// 静态字段,初始为 null
private static DoubleCheckedLockingSingleton instance;
// 静态对象,用于加锁
private static readonly object lockObject = new object();
// 私有构造函数,防止外部实例化
private DoubleCheckedLockingSingleton()
{
Console.WriteLine("DoubleCheckedLockingSingleton 实例已创建");
}
// 公共静态属性,用于获取单例实例
public static DoubleCheckedLockingSingleton Instance
{
get
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
// 示例方法
public void DoSomething()
{
Console.WriteLine("DoubleCheckedLockingSingleton 正在执行操作");
}
}
class Program
{
static void Main()
{
// 获取单例实例
DoubleCheckedLockingSingleton singleton = DoubleCheckedLockingSingleton.Instance;
// 调用示例方法
singleton.DoSomething();
}
}
案例解读
- 第一次检查 :
if (instance == null)
在加锁前先进行检查,如果实例已经创建好了,就直接返回实例,避免了不必要的加锁操作,提高了性能。 - 加锁与第二次检查 :如果第一次检查发现实例为
null
,则进入lock (lockObject)
代码块进行加锁。在加锁后,再次进行if (instance == null)
检查,防止多个线程同时通过第一次检查,在一个线程创建实例后,其他线程不会再重复创建。
优缺点分析
- 优点:既实现了延迟加载,又保证了线程安全,同时减少了不必要的锁操作,提高了性能。
- 缺点:实现相对复杂,需要对多线程编程有一定的理解。
5. 使用 Lazy<T>
实现单例模式(线程安全)
代码示例
using System;
// 使用 Lazy<T> 实现的单例类
public sealed class LazyGenericSingleton
{
// 静态只读的 Lazy<T> 实例
private static readonly Lazy<LazyGenericSingleton> lazy = new Lazy<LazyGenericSingleton>(() => new LazyGenericSingleton());
// 私有构造函数,防止外部实例化
private LazyGenericSingleton()
{
Console.WriteLine("LazyGenericSingleton 实例已创建");
}
// 公共静态属性,用于获取单例实例
public static LazyGenericSingleton Instance
{
get
{
return lazy.Value;
}
}
// 示例方法
public void DoSomething()
{
Console.WriteLine("LazyGenericSingleton 正在执行操作");
}
}
class Program
{
static void Main()
{
// 获取单例实例
LazyGenericSingleton singleton = LazyGenericSingleton.Instance;
// 调用示例方法
singleton.DoSomething();
}
}
案例解读
Lazy<T>
类的使用 :private static readonly Lazy<LazyGenericSingleton> lazy = new Lazy<LazyGenericSingleton>(() => new LazyGenericSingleton());
创建了一个Lazy<T>
实例,使用委托() => new LazyGenericSingleton()
指定了实例的创建方式。- 延迟加载与线程安全 :
Lazy<T>
类会在第一次访问lazy.Value
时才创建实例,实现了延迟加载。同时,Lazy<T>
类内部处理了线程安全问题,确保实例只被创建一次。
优缺点分析
- 优点:实现简单,代码简洁,同时保证了延迟加载和线程安全。
- 缺点 :依赖于 .NET 框架的
Lazy<T>
类,如果需要在不支持该类的环境中使用,就无法采用这种实现方式。
6. 静态内部类实现单例模式(线程安全)
代码示例
using System;
// 静态内部类实现的单例类
public sealed class StaticNestedClassSingleton
{
// 私有构造函数,防止外部实例化
private StaticNestedClassSingleton()
{
Console.WriteLine("StaticNestedClassSingleton 实例已创建");
}
// 静态内部类
private static class Nested
{
// 静态只读字段,保存单例实例
internal static readonly StaticNestedClassSingleton Instance = new StaticNestedClassSingleton();
}
// 公共静态属性,用于获取单例实例
public static StaticNestedClassSingleton Instance
{
get
{
return Nested.Instance;
}
}
// 示例方法
public void DoSomething()
{
Console.WriteLine("StaticNestedClassSingleton 正在执行操作");
}
}
class Program
{
static void Main()
{
// 获取单例实例
StaticNestedClassSingleton singleton = StaticNestedClassSingleton.Instance;
// 调用示例方法
singleton.DoSomething();
}
}
案例解读
- 静态内部类的特性 :在 C# 中,静态内部类只会在第一次被使用时加载。
private static class Nested
是一个静态内部类,其中的internal static readonly StaticNestedClassSingleton Instance = new StaticNestedClassSingleton();
在类加载时创建了StaticNestedClassSingleton
的实例。 - 延迟加载与线程安全 :当第一次访问
StaticNestedClassSingleton.Instance
时,会触发Nested
类的加载,从而创建单例实例。由于静态类的加载是线程安全的,所以这种方式实现了延迟加载和线程安全。
优缺点分析
- 优点:实现简单,代码简洁,同时保证了延迟加载和线程安全。
- 缺点:对于不熟悉静态内部类特性的开发者来说,可能不太容易理解。
7. 通过 Mutex
实现单例模式(系统级单例)
代码示例
using System;
using System.Threading;
// 通过 Mutex 实现的单例类
public sealed class MutexSingleton
{
// 静态字段,保存单例实例
private static MutexSingleton instance;
// 静态 Mutex 实例
private static readonly Mutex mutex = new Mutex(true, "MutexSingleton");
// 私有构造函数,防止外部实例化
private MutexSingleton()
{
Console.WriteLine("MutexSingleton 实例已创建");
}
// 公共静态属性,用于获取单例实例
public static MutexSingleton Instance
{
get
{
if (instance == null)
{
mutex.WaitOne();
try
{
if (instance == null)
{
instance = new MutexSingleton();
}
}
finally
{
mutex.ReleaseMutex();
}
}
return instance;
}
}
// 示例方法
public void DoSomething()
{
Console.WriteLine("MutexSingleton 正在执行操作");
}
}
class Program
{
static void Main()
{
// 获取单例实例
MutexSingleton singleton = MutexSingleton.Instance;
// 调用示例方法
singleton.DoSomething();
}
}
案例解读
Mutex
的使用 :private static readonly Mutex mutex = new Mutex(true, "MutexSingleton");
创建了一个命名的Mutex
实例,true
表示创建线程拥有互斥体的初始所有权,"MutexSingleton"
是互斥体的名称,确保在整个系统中具有唯一性。- 线程同步与实例创建 :在
Instance
属性的get
方法中,mutex.WaitOne()
等待获取互斥量,如果获取到则进入临界区。在临界区内,再次检查实例是否为null
,如果为null
则创建实例。最后,使用mutex.ReleaseMutex()
释放互斥量,允许其他线程或进程获取该互斥量。
优缺点分析
- 优点:可以确保在整个系统中只有一个实例运行,常用于确保应用程序只有一个进程实例。
- 缺点 :
Mutex
是一个系统级的同步原语,使用它会带来一定的性能开销。而且,如果互斥量被异常占用,可能会导致程序出现死锁等问题。