Singleton[设计模式]

题目:设计一个类,我们只能生成该类的一个实例。

分析:只能生成一个实例的类是实现了Singleton模式的类型。

由于设计模式在面向对象程序设计中起着举足轻重的作用,在面试过程中很多公司都喜欢问一些与设计模式相关的问题。在常用的模式中,Singleton是唯一一个能够用短短几十行代码完整实现的模式。因此,写一个Singleton的类型是一个很常见的面试题。

事实上,要让一个类型是能创建一个实例不是一件很难的事情。我们可以把该类型的构造函数设为private,这样该类型的用户就不能创建该类型的实例了。然后我们在给类型中创建一个静态实例。当用户需要该类型的实例时,我们就返回这个实例。基于这个思路,我们可以用C#写出如下代码:

 1 // We can only get an instance of the class Singleton1. The instance
 2     // is created when class Singleton1 is referenced at the first time
 3     public sealed class Singleton1
 4     {
 5         private Singleton1()
 6         {
 7         }
 8  
 9         private static Singleton1 instance = new Singleton1();
10         public static Singleton1 Instance
11         {
12             get
13             {
14                 return instance;
15             }
16         }
17     }

由于类Singleton1 的实例是一个静态变量,因此它会在该类型的第一次引用的时候被创建,而不是第一次在调用Singleton1.get_Instance的时候被创建。如果我们此时并不需要该实例,那么我们就过早地初始化该实例,无论在内存空间还是CPU时间上都是一种浪费。

我们可以把上面的代码稍作改动,就能实现在第一次调用Singleton_getInstance时候才会创建类型的唯一实例:

 

 1  // We can only get an instance of the class Singleton2.
 2     // The instance is created when we need it explicitly.
 3     public sealed class Singleton2
 4     {
 5         private Singleton2()
 6         {
 7         }
 8  
 9         private static Singleton2 instance = null;
10         public static Singleton2 Instance
11         {
12             get
13             {
14                 if (instance == null)
15                     instance = new Singleton2();
16  
17                 return instance;
18             }
19         }
20     }

 

我们在单线程环境下只能得到类型Singleton2的一个实例,但在多线程环境下情况就可能不同了。设想如果两个线程同时运行到语句if (instance == null),而此时该实例的确没有创建,那么两个线程都会创建一个实例。此时,类型Singleton2就不在满足模式Singleton的要求了。

为了保证在多线程环境下我们还是只能得到类型的一个实例,我们应该在判断实例是否已经创建,以及在实例还没有创建的时候创建一个实例的语句上加一个同步锁。我们把Singleton2稍作修改就得到了如下代码:

 

 1  // We can only get an instance of the class Singleton3,
 2     // even when there are multiple threads which are trying
 3     // to get an instance concurrently.
 4     public sealed class Singleton3
 5     {
 6         private Singleton3()
 7         {
 8         }
 9  
10         private static readonly object syncObj = new object();
11  
12         private static Singleton3 instance = null;
13         public static Singleton3 Instance
14         {
15             get
16             {
17                 lock (syncObj)
18                 {
19                     if (instance == null)
20                         instance = new Singleton3();
21                 }
22  
23                 return instance;
24             }
25         }
26     }

 

说明一下,由于C/C++没有为线程同步提供直接的支持。为了让代码显得简洁,而不是让大量的代码在实现同步锁而偏离了实现Singleton的主题,本文的代码用C#实现。

我们还是假设有两个线程同时想创建一个实例。由于在一个时刻只能有一个线程能得到同步锁。当第一个线程加上锁时,第二个线程只能在等待。当第一个线程发现实例还没有创建时,它创建出一个实例。接着第一个线程释放同步锁。此时第二个线程可以加上同步锁,并运行接下来的代码。由于此时实例已经被第一个线程创建出来了,第二个线程就不会重复创建实例了。于是保证了我们只能得到一个实例。

但是类型Singleton3还不是完美。由于我们每次调用Singleton3.get_Instance的时候,都会试图加上一个同步锁。由于加锁是一个非常耗时的操作,在没有必要的时候我们应该尽量避免这样的操作。

实际上,我们只是在实例还没有创建之前需要加锁操作,以保证只有一个线程创建出实例。而当实例已经创建之后,我们已经不需要再做加锁操作了。于是,我们可以把上述代码再作进一步的改进:

 

 1  // We can only get an instance of the class Singleton4,
 2     // even when there are multiple threads which are trying
 3     // to get an instance concurrently. When the instance has
 4     // been created, we don't need the lock any more.
 5     public sealed class Singleton4
 6     {
 7         private Singleton4()
 8         {
 9         }
10  
11         private static object syncObj = new object();
12  
13         private static Singleton4 instance = null;
14         public static Singleton4 Instance
15         {
16             get
17             {
18                 if (instance == null)
19                 {
20                     lock (syncObj)
21                     {
22                         if (instance == null)
23                             instance = new Singleton4();
24                     }
25                 }
26  
27                 return instance;
28             }
29         }
30     }

 

我们只需要在最开始调用Singleton4_getInstance(可能来自一个线程,也可能来自多个线程)的时候需要加锁。当实例已经创建之后,我们就不再需要作加锁操作,从而在后续调用Singleton4_getInstance时性能得到提升。

关于第一种写法的更多,请参考http://en.wikipedia.org/wiki/Double-checked_locking。站在面试的角度,本文的分析已经足够应付。但如果想展示更多对多线程编程的理解,更深入地了解这个问题总是有益的。

 

以上摘自何海涛博客

posted @ 2012-08-11 19:50  wolenski  阅读(297)  评论(2编辑  收藏  举报