需求:需要一个类,这个类只能有一个实例。
首先看一个很简单的例子
Code
1public class Singleton
2{
3 private static Singleton instance;
4
5 private Singleton() { }
6
7 public Singleton Instance
8 {
9 get
10 {
11 if (instance == null)
12 {
13 instance = new Singleton();
14 }
15
16 return instance;
17 }
18 }
19}
这个例子是在简单不过的了.这里用了一个属性,其实改用方法也一样,如下
Code
1public class Singleton
2 {
3 private static Singleton instance;
4
5 private Singleton() { }
6
7 public Singleton GetInstance()
8 {
9 if (instance == null)
10 {
11 instance = new Singleton();
12 }
13
14 return instance;
15 }
16 }
17
18
注意,这里用了一个技巧,或者也可以称做一种模式.那就是Lazy-Allocate(缓分配),或者Lazy-Load.
if(instance == null)
{
instance = new Singleton();
}
.NET Framework中大量的使用了这种方法,Lazy-Allocate的设计概念很简单,就是未使用前不预付成本。
但是这种方式在单线程下确实运行的很好.一旦到了多线程就可能出现多个实例
需求:多线程环境下的单例
Code
public class Singleton
{
// volatile 实例的指令不被编译器重新调整
private static volatile Singleton instance;
private static object lockHelper = new object();
private Singleton() { }
public Singleton Instance
{
get
{
// double check
if (instance == null)
{
lock (lockHelper)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
首先多线程下判断instance为null的时候才可以进行实例,而实例化的时候要加一个锁
这里lockHelper,可以是你自己的有用的代码,也可以是一个仅仅起标识作用的不参与运算的对象
然后在判断是否实例化.这个方法也叫Double Check.
大家注意到private static volatile Singleton instance;多了一个关键字 volatile,这个是什么用的呢
就是在编译的时候,不要让Runtime调整编译指令的顺序
因为DotNet在编译的时候会对指令进行优化调整.所以即便用了Double Check还是有很小的机会创建多个Singleton的实例
所以我们加上volatile关键字,让Runtime不对这个instance做指令调整.这样就完全实现了多线程的Singleton.
有没有更简洁一点的用法呢.
看下面的例子
Code
1 public class Singleton
2 {
3 private static readonly Singleton instance = new Singleton();
4
5 private Singleton() { }
6
7 public Singleton Instance
8 {
9 get
10 {
11 return instance;
12 }
13 }
14 }
15
16
这样就可以实现在多线程环境下的Singleton模式.为什么?
回忆一下readonly的用法,他本身就是只能进行一次初始化,然后就和const没什么区别.
那么还有一个问题.如果我的构造函数是有参数的时候该怎么办呢.
Code
public class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public Singleton Instance
{
get
{
return instance;
}
}
public int X { get; set; }
}
这样本来需要在构造函数中需要的参数,我们把它改成一个属性.让这个属性是可以修改的..
噢,还可以考虑的解决方案.但是问题还是有.
比如有时候一些参数是有依赖性的.最好放在构造函数执行该怎么办呢.
看下面的代码
Code
public class Singleton
{
private static readonly Singleton instance = new Singleton();
private static bool instanced = false;
private static object lockHelper = new object();
private Singleton() { }
public Singleton Instance
{
get
{
if (instanced)
return instance;
else
throw new Exception("");
}
}
public int X { get; set; }
public void Init(int x)
{
if (!instanced)
{
lock (lockHelper)
{
if (!instanced)
{
this.X = x;
instanced = true;
}
}
}
}
}
首先在私有构造函数中,我们把要执行的code转移到Init方法中,让私有构造函数是空.
这样我们保证了它是一个Singleton
其次必须使用Init来初始化这个对象.如果没有初始化就引用,这个时候抛出异常.
第三.Init方法也只执行一次.
这样一个保证多线程环境下,任意参数的构造的一个Singleton模式就完成了.
当然设计模式就是一种思想,不能生搬硬套.具体环境具体操作.
在单例模式的使用中,要注意一下几个方面:
Singleton模式中的构造器也可以设置为protected允许子类派生
Singleton模式一般不要支持ICloneable接口,避免导致多个对象实例
Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例
小心应对多线程环境;
Singleton的关键是只能生成一个实例,在推敲一下,就是只能生成固定数量的实例,那么这种情况下,Singleton就是一个特例.
关于生成固定数量的实例,有兴趣的朋友可以自行研究一下.