一、概述
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。而设计模式中最常见的大概就是单例模式了,关于单例模式的文章有很多,有的已经讲得非常好了。本文试图从3W的角度来说明单例模式。二、主要内容
1.为什么需要单例模式 (why)
单例模式的存在是理所当然的。比如中国有很多皇帝,但是同一时期只能有一个皇帝,那个时期的百姓一谈皇帝就知道是说的哪一个皇帝。又比如你是一个富翁,你有很多汽车,但是某一时刻你只能从车库里拿出一辆车坐在里面,不可能坐在两辆车里,除非你有分身术。再比如画画程序的工具箱,注册表编辑器,打印程序等。用专业的话来说,就是某一个时刻,一个类只能有一个实例化的对象。2.什么是单例模式(what)
单例模式的定义如下:“ensure a class has only one instance,and provide a global point of access to it.”(确保一个类只有一个实例,并提供一个访问它的全局访问点)关于这句话,有以下几点需要说明:
(1)单例类有且只有一个实例。说到这里,我们需要提一下静态类了。静态类是不能实例化的(任何地方都不能),它只有静态的属性和方法。静态类主要用于共享和提供通用的功能,例如提供数学计算的math类。单例模式中的类不能在类之外实例化,但是可以在类里面实例化。这也正是它为什么能够提供一个实例的原因。
(2)既然不允许外部new,那么需要将构造函数私有化。
(3)外部要能访问这个实例化的对象。很简单,我们只需要提供一个静态方法,供外部访问该实例即可。
然后我再来看类图就十分轻松了:
3.如何使用单例模式(how)
通过单例模式的定义及分析,写出单例模式的类就很容易了。 public sealed class Singleton{
private static readonly Singleton singleton = new Singleton ();//内部实例化一个对象
private Singleton (){}//将构造函数私有化,防止外部创建对象
public static Singleton getInstance(){return singleton;}//提供一个访问点
}
我们来分析这段代码,sealed关键字是防止类派生,因为派生也可能产生出新的实例。readonly关键字表明字段只能作为声明的一部分或者构造函数中出现。当整个类被加载的时候,就会自行初始化 singleton 这个静态只读变量。也就是说,不管我们用没用到,它就已经开始存在了,而不是我们需要的时候才去实例化一个对象出来。这种方式称之为:饿汉单例模式。如果我们再懒一点,想在需要它的时候再实例化呢?这个不难实现,只需要稍微做一下改动就好了:
public class Singleton{
private static Singleton singleton;
private Singleton (){}//将构造函数私有化,防止外部创建对象
public static Singleton getInstance()//提供一个访问点
{
if(singleton==null)//若实例不存在则创建实例,否则返回该实例
{
singleton = new Singleton()
}
return singleton;
}
}
这个在需要时才创建实例的方式称之为:懒汉单例模式。似乎问题解决了,但是这个是线程不安全的。在单线程下,这个没有任何问题,在多线程下面就可能会出现多个实例!原因就在于singleton==null的判断。例如线程A执行到singleton==null,发现没有实例,于是开始创建新实例,在还没创建完成的时候,线程B恰好也执行到singleton==null,也发现没有实例,于是也开始创建新实例,这样就会出现两个实例!这能叫单例模式吗?显然不能!
怎么解决呢?其实,我们可以通过给正在创建实例的线程加上一把锁,这样别的线程就无法创建新的实例了。关于lock的使用可以参考MSDN,地址如下:http://msdn.microsoft.com/zh-cn/library/c5kehkcz(VS.80).aspx
正确的饿汉单例模式:
public class Singleton
{
private static Singleton singleton;
private static readonly object syncObject = new object();
private Singleton() { }
public static Singleton GetInstance()
{
if (singleton == null)
{
lock (syncObject)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
}
关于这段代码,有以下几点需要说明:
lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围,singleton 最初为 null ,是无法实现加锁的,所以必须要一个实例化的对象即 syncObject 来定义加锁的范围。
2.在 GetInstance()中,在 if 语句中使用两次判断 singleton == null ,这叫做双重检查锁定。
先解释第二个singleton == null 的必要性,它是为了防止出现多个实例而设置的。假设线程A顺利到达第二个singleton == null,发现没有实例,于是乎开始创建实例。创建实例的时候线程B通过第一个singleton == null的判断,顺利到达lock,发现被锁住了,于是B被阻塞。当A创建实例完成后,解锁,此时B阻塞解除顺利进入,如果没有第二个singleton == null的判断,那么B就要开始创建实例了,那么就会同时存在两个实例!所以正确的做法是,线程B再次判断singleton == null,发现有实例了,于是直接返回该实例即可。
再来解释第一个singleton == null。没有第一个singleton == null的判断,程序其实是正常的,通过分析就可以知道。而问题在于,如果没有这个“多余”的判断,在多线程下,每个线程到这里都会执行加锁的操作,这是不必要的,而且是耗费性能的。
饿汉单例和懒汉单例的使用,要根据实际情况。不过引用《大话设计模式》中的一句话:“从c#语言角度来讲,饿汉式的单例类已经足够满足我们的需求了。”
附:单例模式程序例子
namespace 单例模式
{
public sealed class singleton
{
private static readonly singleton sig = new singleton();
private singleton() { }
public static singleton GetInstance()
{
return sig;
}
public static void declare()
{
Console.WriteLine("我是单例模式!");
}
}
class Program
{
static void Main(string[] args)
{
singleton sig1 = singleton.GetInstance();
singleton sig2 = singleton.GetInstance();
if (sig1.Equals(sig2))
Console.WriteLine("是同一个实例!");
else
Console.WriteLine("不是同一个实例!");
Console.Read();
}
}
}
参考文献:
1.《大话设计模式》2《设计模式之禅》
3.《HEAD FIRST 设计模式》
3.http://blog.sina.com.cn/s/blog_48a45b950100j68w.html4.http://www.cnblogs.com/BoyXiao/archive/2010/05/07/1729376.html?login=1(建议也看看评论,评论更精彩)
5.http://blog.youkuaiyun.com/hackbuteer1/article/details/7460019
6.http://blog.youkuaiyun.com/zhengzhb/article/details/7331369
7.http://msdn.microsoft.com/zh-cn/library/c5kehkcz(VS.80).aspx
8.http://www.cnblogs.com/jeffreyzhao/archive/2009/09/02/double-check-failure.html