单例模式(Singleton pattern)

一、什么是单例模式

单例模式(Singleton)是几个创建模式中最对立的一个,它的主要特点不是根据用户程序调用生成一个新的实例,而是控制某个类型的实例唯一性,通过下图我们知道它包含的角色只有一个,就是Singleton,它拥有一个私有构造函数,这确保用户无法通过new直接实例它。除此之外,该模式中包含一个静态私有成员变量instance与静态公有方法Instance()。Instance()方法负责检验并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。 

 

 

二、单例模式的特点

  • 有一个私有的无参构造函数,这可以防止其他类实例化它,而且单例类也不应该被继承,如果单例类允许继承那么每个子类都可以创建实例,这就违背了Singleton模式“唯一实例”的初衷。
  • 单例类被定义为sealed,就像前面提到的该类不应该被继承,所以为了保险起见可以把该类定义成不允许派生,但没有要求一定要这样定义。
  • 一个静态的变量用来保存单实例的引用。
  • 一个公有的静态方法用来获取单实例的引用,如果实例为null即创建一个。

 

 

三、单例模式的优点

  • 实例控制:单例模式防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
  • 伸缩性:因为由类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。

 

 

四、单例模式的缺点

  • 系统开销。虽然这个系统开销看起来很小,但是每次引用这个类实例的时候都要进行实例是否存在的检查。这个问题可以通过静态实例来解决。
  • 开发混淆。当使用一个单例模式的对象的时候(特别是定义在类库中的),开发人员必须要记住不能使用new关键字来实例化对象。因为开发者看不到在类库中的源代码,所以当他们发现不能实例化一个类的时候会很惊讶。
  • 对象生命周期。单例模式没有提出对象的销毁。在提供内存管理的开发语言(比如,基于.NetFramework的语言)中,只有单例模式对象自己才能将对象实例销毁,因为只有它拥有对实例的引用。在各种开发语言中,比如C++,其它类可以销毁对象实例,但是这么做将导致单例类内部的指针指向不明。

 

 

五、单例模式的适用场景

  • 使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反之,如果一个类可以有几个实例共存,就不要使用单例模式。
  • 不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。
  • 不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。 

 

 

 六、单例模式的实例

实例1:可以考虑实现单例模式的地方还有很多,例如:
1) 对于计算机的外部资源打印机的情况,因只有一个Printer Spooler,为避免两个打印作业同时输出到打印机中,可考虑用单例模式实现。
2) Window的回收站在整个系统中只有唯一的一个实例,而且回收站自行提供自己的实例,回收站也是单例模式的应用 

单例类(打印服务) 

 1 public class PrintSploor
2 {
3 //定义一个【私有的】实例方法,预防通过PrintSploor s=new PrintSploor(); 这样就做不到单例
4 private PrintSploor() { }
5
6 //创建一个私有的实例对象,保证是唯一的
7 private static PrintSploor instance = null;
8
9 //提供一个获取实例的公共方法
10 public static PrintSploor Instance
11 {
12 get
13 {
14 if (null == instance)
15 {
16 instance = new PrintSploor();
17 }
18
19 return instance;
20 }
21 }
22 }

客户端代码

 1 public class Program
2 {
3 static void Main(string[] args)
4 {
5 PrintSploor printSploor1 = PrintSploor.Instance;
6 PrintSploor printSploor2 = PrintSploor.Instance;
7
8 if (printSploor1 == printSploor2)
9 {
10 Console.WriteLine("说明是同一对象,是单例。");
11 }
12 else
13 {
14 Console.WriteLine("说明不是同一对象,不是单例。");
15 }
16
17 Console.Read();
18 }
19 }

以上代码完成了在单线程程序中的单例模式,如果我们现在的程序是多线程的话,还是用这个单例类的话,就有可能会出现PrintSploor类的多个程序。那我们又该怎么避免呢?请看以下几种代码的实现:

方法一:

首先我们创建了一个静态只读的进程辅助对象,由于lock是确保当一个线程位于代码的临界区时,另一个线程不能进入临界区(同步操作)。如果其他线程试图进入锁定的代码,则它将一直等待,直到该对象被释放。从而确保在多线程下不会创建多个对象实例了。 

 1 public class PrintSploor
2 {
3 //定义一个【私有的】实例方法,预防通过PrintSploor s=new PrintSploor(); 这样就做不到单例
4 private PrintSploor() { }
5
6 //创建一个私有的实例对象,保证是唯一的
7 private static PrintSploor instance = null;
8
9 private static object syncObject = new object();
10
11 //提供一个获取实例的公共方法
12 public static PrintSploor Instance
13 {
14 get
15 {
16
17 lock (syncObject)
18 {
19 if (null == instance)
20 {
21 instance = new PrintSploor();
22 }
23 }
24
25 return instance;
26 }
27 }
28 }

方法二:

前面讲到的线程安全的实现方式的问题是要进行同步操作,那么我们是否可以降低通过操作的次数呢?其实我们只需在同步操作之前,添加判断该实例是否为null就可以降低通过操作的次数了,这样是经典的Double-Checked Locking方法。 

 1 public class PrintSploor
2 {
3 //定义一个【私有的】实例方法,预防通过PrintSploor s=new PrintSploor(); 这样就做不到单例
4 private PrintSploor() { }
5
6 //创建一个私有的实例对象,保证是唯一的
7 private static PrintSploor instance = null;
8
9 private static object syncObject = new object();
10
11 //提供一个获取实例的公共方法
12 public static PrintSploor Instance
13 {
14 get
15 {
16 if (null == instance)
17 {
18 lock (syncObject)
19 {
20 if (null == instance)
21 {
22 instance = new PrintSploor();
23 }
24 }
25 }
26
27 return instance;
28 }
29 }
30 }

方法三:

通过在类中创建instance静态字段的时候直接new出一个实例来,然后通过Instance属性来实现唯一获取该类实例的返回点,从而实现PrintSploor只有一个实例。达到单例模式的效果 

 1 public class PrintSploor
2 {
3 //定义一个【私有的】实例方法,预防通过PrintSploor s=new PrintSploor(); 这样就做不到单例
4 private PrintSploor() { }
5
6 //创建一个私有的实例对象,保证是唯一的
7 private static PrintSploor instance = new PrintSploor();
8
9 private static object syncObject = new object();
10
11 //提供一个获取实例的公共方法
12 public static PrintSploor Instance
13 {
14 get
15 {
16 return instance;
17 }
18 }
19 }
posted @ 2012-03-29 23:58  技术勇者  阅读(567)  评论(0编辑  收藏  举报