.Net设计模式之单例模式
单例模式的介绍
在软件的开发过程中,很多时候,我们需要对一个类进行实例化后,再使用,有时这个类比较简单,有时也可能会很复杂,但不管怎样,为了保证软件的质量和效率,大多数时候,我们只希望它被实例化一次,所以这就需要引入单例模式(Singleton Pattern)了。单例模式,即保证一个类仅有一个实例,并提供一个访问它的全局访问点。那,怎么做到呢?为不不被实例化多个对象,就可以让类自身负责保存它的唯一实例。软件中重复使用某个类时,为了防止多次实例化产生的资源消耗,这个时候就应该使用单例设计模式了。如:网络请求、数据库操作等。
上面为单例的类图
单例要点
- 构造函数不对外开放,一般为 private;
- 通过一个静态方法或者枚举返回单例对象;
- 确保单例类的对象有且只有一个,尤其是在多线程环境下;
- 确保单例类对象在反序列化时不会被重新构建对象。
代码示例
简单实现
public class Singleton
{
private static Singleton instance;
/// <summary>
/// 构造方法为private,可以僻免外界通过利用new创建此类实例的可能
/// </summary>
private Singleton() { }
/// <summary>
/// 此方法为获得本类实例的唯一全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
if (instance == null)
{
return instance;
}
return instance;
}
}
上面的写法,可以实现单例模式,在单线程的情况下,并没有问题,但如果在多线程的环境下,两个或更多的线程同时判断 instance == null 为 true, 那么这个类仍然可以被多次实例化,那么它是不安全的,并不是真正的单例。这个时候,我们就会想到,可以在这之前,加上 lock 解决问题。提示代码如下:
线程安全
public class Singleton
{
private static readonly object locker = new object();
private static Singleton instance;
/// <summary>
/// 构造方法为private,可以僻免外界通过利用new创建此类实例的可能
/// </summary>
//private Singleton() { }
/// <summary>
/// 此方法为获得本类实例的唯一全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
lock (locker)
{
if (instance == null)
{
return instance;
}
}
return instance;
}
}
lock( locker) // 在同一时刻加了锁的那部分程序,只有一个线程可以进入,其他线程要进入,只能待已进行的线程退出lock程序块后,才能进入,这就保证了线程安全,不会创建多个实例。现在线程安全是解决了,但还是有个小问题,每次获取实例的时候,都需要加锁,然后才能判断实例是否被创建了,怎么解决这个问题?这就需要再引入一个 双重锁 的做法了。
双重锁定
/// <summary>
///
/// </summary>
public class Singleton
{
private static readonly object locker = new object();
private static Singleton instance;
/// <summary>
/// 构造方法为private,可以僻免外界通过利用new创建此类实例的可能
/// </summary>
//private Singleton() { }
/// <summary>
/// 此方法为获得本类实例的唯一全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 判断1
if (instance == null)
{
lock (locker)
{
// 判断2
if (instance == null)
{
return instance;
}
}
}
return instance;
}
}
上面的代码,在lock(locker) 前后,都加入了 对实例是否创建的判断, 判断1 主要是考虑到在实例已经被创建后,僻免程序再锁定 后面的程序块,减少资源的浪费,判断2 是为了 在没有创建实例的情况下才创建实例。经过多次改造后的单例,看似完美,但还是在获取实例的时候需要去判断实例是不是被创建了,那么,有没有别的方式每次都去判断呢?答案是有的,C#与公共语言运行库也提供了一种 "静态初始化" 方法,这种方法不需要开发人员显式地编写线程安全代码。
静态初始化
/// <summary>
/// 这里用到了 sealed 主要是不让其他类继承,而继承可能会增加实例
/// </summary>
public sealed class Singleton
{
//在第一次引用类的任何成员时创建实例,由公共语言运行库负责处理变量的初始化
private static readonly Singleton instance = new Singleton();
//构造函数私有化,是为了不被通过 new 达到实例化的目的
private Singleton() { }
public static Singleton GetSingleton() {
return instance;
}
}
这个实现方式与前面的示例类似,都是解决了单例模式试图解决的两个基本问题:全局访问和实例化控制,公共静态属性是为访问实例提供了一个全局访问点。不同的地方在于它依赖公共语言运行库来初始化变量。再就是它的构造方法标记为私有,所以不能在类本身以外的地方通过 new 来实例化 Singleton 类;因此变量引用的是可以在系统中存在唯一的实例。值得注意的是,instance 变量标记为 readonly,也就是只能在静态初始化期间 或 在类的构造函数中分配变量。关于 sealed 的关键字,也可以在上面几个类中使用,只是类可能要稍加改动。
最后,感觉各位能花时间看到这里,谢谢,欢迎留言交流。
参考文献:
大话设计模式 程杰 著