内容摘录自程杰的《大话设计模式》
单例模式
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局方法。
所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效。所以将单例类的构造方法写成private的,外部程序就不能用new来实例化它了。
然后我们在单例类中通过public方法GetInstance(),来返回一个类实例。
1、单例类的简单实现代码
class Singleton
{
private static Singleton instance;
private Singleton() // private构造函数,堵死外界利用new创建此类实例的可能
{
}
public static Singleton GetInstance() // 此方法是获得本类实例的唯一全局访问点
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
单例模式封装它的唯一实例,这样它可以严格地控制客户怎样访问它以及何时访问它。简单的说就是对唯一实例的受控访问。
实用类通常也会采用私有化的构造方法来避免其有实例。但是它们还是有很多不同的,比如实用类不保存状态,仅提供一些静态方法或静态属性让你使用,而单例类是有状态的。实用类不能用于继承多态,而单例虽然实例唯一,却是可以有子类来继承。实用类只不过是一些方法属性的集合,而单例却是有着唯一的对象实例。在运用中还得仔细分析再做决定用哪一种方式。
2、多线程时的单例
在多线程的程序中,多个线程可能会同时访问Singleton类,调用GetInstance()方法,会有可能造成创建多个实例的。
这可以通过给进程加一把锁来处理。锁,即lock语句,lock是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
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;
}
}
这样每次调用GetInstance方法时都需要lock,会影响性能。
3、双重检测锁定(Double-Check Locking)
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;
}
}
在外面已经判断了instance实例是否存在,为什么在lock里面还需要在做一次判断呢?
当instance为null,并且同时有两个线程调用GetInstance()方法时,它们将都可以通过第一重instance==null的判断。然后由于lock机制,这两个线程则只有一个进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入。而此时如果没有了第二重的instance是否为null的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的了。
4、静态初始化
C#与公共语言运行库也提供了一种‘静态初始化’方法,这种方法不需要开发人员显式地编写线程安全代码,即可解决多线程环境下它是不安全的问题。
public sealed class Singleton // sealed阻止发生派生,而派生可能会增加实例
{
private static readonly Singleton instance = new Singleton(); // 在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化
private Singleton()
{
}
public static Singleton GetInstance()
{
return instance;
}
}
instance变量被标记为readonly,这意味着只能在静态初始化期间或在类构造函数中分配变量。由于这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象的称之为饿汉式单例类,原先的单例模式处理方式是要在第一次被引用时,才会将自己实例化,所以被称为懒汉式单例类。
懒汉式:类一加载就实例化的对象,所以要提前占用系统资源。
懒汉式:会面临着多线程访问的安全性问题,需要做双重判断锁定的处理。