什么是单例模式?
从“单例”字面意思上理解为——一个类只有一个实例,所以单例模式也就是保证一个类只有一个实例的一种实现方法罢了。
其官方定义为:确保一个类只有一个实例,并提供一个全局访问点。
为什么会有单例模式?
从单例模式的定义中我们可以看出——单例模式的使用自然是当我们的系统中某个对象只需要一个实例的情况。
剖析单例模式实现思路
- 明确目的:(1)确保一个类只有一个实例;(2)提供一个访问它的全局访问点;
- 类的实例化基本都是通过关键字new的,而定义私有的构造函数就不能在外界通过new创建实例,类实例的创建在类里面;
- 每个线程都有自己的线程栈,定义一个静态私有变量保存类的实例主要是为了在多线程确保类有一个实例;
- 定义一个公有静态方法是为了公开类的实例;
简单代码实现如下:
/// <summary>
/// 单例模式(确保一个类只有一个实例,并提供一个全局访问点)
/// </summary>
public sealed class Singleton
{
/// <summary>
/// 私有静态变量保存类的唯一实例
/// </summary>
private static Singleton uniqueInstance;
/// <summary>
/// 私有构造方法,避免外部 new
/// </summary>
private Singleton() { }
/// <summary>
/// 暴露全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
if (uniqueInstance == null)
uniqueInstance = new Singleton();
return uniqueInstance;
}
}
上面的单例模式的实现在单线程下确实是完美的,然而在多线程的情况下会得到多个Singleton实例,因为在两个线程同时运行GetInstance方法时,此时两个线程判断(uniqueInstance ==null)这个条件时都返回真,此时两个线程就都会创建Singleton的实例,这样就违背了我们单例模式初衷了,既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使GetInstance方法在同一时间只运行一个线程运行就好了,也就是我们线程同步的问题了,具体的解决多线程的代码如下:
/// <summary>
/// 单例模式(确保一个类只有一个实例,并提供一个全局访问点,线程同步)
/// </summary>
public sealed class Singleton_MultiThread
{
/// <summary>
/// 私有静态变量保存类的唯一实例
/// </summary>
private static Singleton_MultiThread uniqueInstance;
/// <summary>
/// 锁,确保线程同步
/// </summary>
private static readonly object locker = new object();
/// <summary>
/// 私有构造方法,避免外部 new
/// </summary>
private Singleton_MultiThread() { }
/// <summary>
/// 暴露全局访问点
/// </summary>
/// <returns></returns>
public static Singleton_MultiThread GetInstance1()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (locker)
{
if (uniqueInstance == null)
uniqueInstance = new Singleton_MultiThread();
}
return uniqueInstance;
}
/// <summary>
/// 暴露全局访问点(双重锁定,减小开销,提升性能)
/// </summary>
/// <returns></returns>
public static Singleton_MultiThread GetInstance2()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
// 双重锁定只需要加一句判断就可以了
if (uniqueInstance == null)
{
lock (locker)
{
if (uniqueInstance == null)
uniqueInstance = new Singleton_MultiThread();
}
}
return uniqueInstance;
}
}
上面这种解决方案确实可以解决多线程的问题,但是上面GetInstance1()对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(uniqueInstance==null)为假,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能,为了改进上面实现方式的缺陷,我们只需要在lock语句前面加一句(uniqueInstance==null)的判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它 “双重锁定”,可参考GetInstance2()代码。
单例模式的其他实现方法
静态初始化
public sealed class Singleton_StaticInit
{
private static readonly Singleton_StaticInit _instance = new Singleton_StaticInit();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton_StaticInit()
{
}
/// <summary>
/// Prevents a default instance of the
/// <see cref="S