1.概述
单例模式(Singleton Pattern)又称单件模式,单例模式保证一个类仅有一个实例,并提供一个访问的他的全局访问点。通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化对个对象,一个最好的办法就是,让类自身负责保存他的唯一实例,这个类可以保证没有其他实例可以被创建并且他可以提供一个访问该实例的方法。
使用场合:当类只能有一个实例存在,并且可以在全局访问时,这个唯一的实例应该可以通过子类实现扩展,并且用户无须更改代码即可使用。前面学习过得工厂类经常被实例化为全局唯一的单件,可能的单件还有系统的管理日志的对象、关键字生成对象和外部设备接口对象等。
结构:
Singleton
public class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
public class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
单例模式提供了全局唯一的访问入口,易于控制可能发生的冲突。
单例模式因为Singleton类封装它的唯一实例,这样他可以严格的控制客户怎样访问它以及何时访问他。
单例模式是对静态方法的一种改进,首先避免了全局变量对系统的污染,其次他可以有子类,可以定义虚方法,具有多态性。而类中的静态方法是不能被定义为虚方法,因此不具有多态性。
2.实例
1.单件计数器
最简单的单例模式应用就是计数器,在基于Web的应用中,我们希望有一个全局计数器来统计单件的次数,为此我们要定义一个单件计数器:
Counter
/**////
/// 计数器
///
public class Counter
{
private int intCounter = 0;
private static Counter MyCounter;
public int Count()
{
intCounter = intCounter + 1;
return intCounter;
}
public static Counter GetInstance()
{
if (MyCounter == null)
{
MyCounter = new Counter();
}
return MyCounter;
}
}
/**////
/// 计数器
///
public class Counter
{
private int intCounter = 0;
private static Counter MyCounter;
public int Count()
{
intCounter = intCounter + 1;
return intCounter;
}
public static Counter GetInstance()
{
if (MyCounter == null)
{
MyCounter = new Counter();
}
return MyCounter;
}
}
调用代码:
for
(
int
i
=
0
; i
<
10
; i
++
)
{
Console.WriteLine(Counter.GetInstance().Count());
}
2.多线程时的单例以及双重锁定
{
Console.WriteLine(Counter.GetInstance().Count());
}
在多线程程序中,多个线程同时访问Singleton类时,调用GetInstance()方法,会有可能造成创建多个实例,我们可以给进程加一把锁来处理。 锁(Lock)是确保当一个线程位于代码的临界区时,另一个线程不能进入该临界区,如果其他线程试图进入锁定的代码,则他将一直等待(即被阻止),直到该对象被释放【MSDN】。
下面来看加锁的单例模式:
Singleton
public class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
return instance;
}
}
public class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
return instance;
}
}
上面的代码中的对象实例由最先进入的那个线程创建,以后的线程在进入时不会再去创建对象实例了。由于有了Lock,就保证了多线程环境下的同时访问也不会造成多个实例的生成。
我们不经要问,为什么不直接lock呢,而是创建一个SyncRoot来Lock呢?因为在我们加锁的时候,instance实例有没有被创建过实例我们都还不知道呢,我们不可能对其加锁的。上面我们=每次调用GetInstance()方法时,都需要Lock,这样会影响性能的。所以我们再做一定的改良优化,看下面的代码:
Singleton
public class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
现在,我们就不用让线程每次都加锁了,而只是在实例未被创建的时候才加锁,同时也保证了多线程的安全,这种做法就是Double-Check Locking (双重锁定)。到这儿我们不经会问,我在外面已经判断了instance实例是否存在,为什么在lock里面还要判断呢?我们具体的来分析,对于instance存在的情况,就直接返回,这没有问题,当instance位null并且同时有两个线程调用GetInstance()方法时,他们同时会通过第一重判断,然后由于Lock机制,则这两个线程只能进入一个,另一个则只能排队等候,必须其中一个进入并出来后,他才能进入。而此时如果没有了第二重的判断,则第一个线程创建了实例,而第二个线程还是可以继续创建新的实例,这样就没有达到单例模式的目的。
public class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
3.静态初始化
在.Net中公共语言运行库中也提供了一种“静态初始化”方法,这种方法不需要显示的编写线程安全代码,即可解决多线程环境下单例模式不安全的问题.看下面简单的代码:
Singleton
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public static Singleton GetInstance()
{
return instance;
}
}
这种静态初始化的方法是自己被加载时就自己实例化,被形象的称之为恶汉式单例类。而原先的单例模式处理方式要在第一次被引用的时候才会被实例化,就被称为懒汉式单例类。
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public static Singleton GetInstance()
{
return instance;
}
}
3.总结
全局变量与单例模式:单例模式维护了自身的实例化,在使用上是安全的,一个全局对象无法自行维护,也就无法规避重复创建对象实例,系统资源会被大量占用,更糟糕的是会出现逻辑问题。
单件与实用类中的静态方法:
实用类是提供系统公用的静态方法,并且也经常采用私有的构造方法,与单例不同,他没有实例,其中的方法全是静态方法。实用类和单例模式的区别如下:
1).实用类不保存状态,仅提供功能。2)实用类不具有多态性,而单例类可以有子类。3)单例是对象,而实用类只是方法的集合。