【总结】设计模式应用之单例模式

1、前言

单例模式的应用场景十分清晰,就是一句话,在整个的软件运行周期内,对于某个类只能容许有零个或一个实例。单例模式应用十分普遍,好比咱们电脑上的任务管理器就是一个单例模式,不管开多少个任务管理器,你会发现只有一个窗口,这就是典型的单例模式的应用;还有,网站的访问次数统计,若是不采用单例模式会很难统计;多线程的线程池的设计通常也是采用单例模式,这是因为线程池要方便对池中的线程进行控制;Web应用的配置对象的读取,通常也应用单例模式,这是因为配置文件是共享的资源;HttpApplication 也是单位例的典型应用,若是你了解ASP.Net(IIS)的整个请求生命周期,就会发现其实HttpApplication也是单例模式,全部的HttpModule都共享一个HttpApplication实例。这样的例子实在太多了,那么比较一下就会发现这些例子的共同点:资源共享的时候,适合使用单例,避免多个对象竞争产生死锁,虽然能够不使用单例,可是对性能仍是或多或少有些影响的。html

2、单例模式的实现

一、单例模式的构造函数是私有的,为了不外部使用new操做符进行实例化而违背这一设计的初衷;设计模式

二、单例模式必须有一个全局访问点,用于获取当前的实例;多线程

既然单例模式是这样,那就写一下代码看看:函数

 1 public class SingleTon
 2 {
 3     private static SingleTon _singleTon;
 4     //私有构造,避免new操做符
 5     private SingleTon()
 6     {
 7 
 8     }
 9 
10     public static SingleTon GetSingleInstance()
11     {
12         //若是_singleTon是空的,那么建立一个实例并返回
13         if (_singleTon == null)
14         {
15             _singleTon = new SingleTon();
16         }
17         return _singleTon;
18     }
19 }

下面是客户端调用代码:性能

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         SingleTon s1 = SingleTon.GetSingleInstance();
 6         SingleTon s3 = SingleTon.GetSingleInstance();
 7         Console.WriteLine(s1.Equals(s3));//返回True
 8         Console.ReadLine();
 9     }
10 }

运行结果返回True,这是很明显的。由于SingleTon第一次调用该类的GetSingleInstance方法时,进入到了if语句块并建立了实例,而再次调用GetSingleInstance方法的时候,已经存在实例了,就直接把_singleTon返回了,因此s1和s2指向的实际上是同一个实例。你觉得单例模式就这么完事儿了?远没有这么简单,上面的情形只是在单线程状况下,假设这样一个场景,若是在多线程的应用程序中,刚好有两个线程同时访问该类的GetSingleInstance方法,当第一个线程进入的时候,此时的_singleTon仍是null,第二个线程进入的时机就是在第一个线程已经进入if语句块可是尚未建立实例的时候,此时问题就来了,两个线程进入了if语句那么必然会建立两个实例,很明显这不符合单例模式的逻辑。那怎么样才能避免上面的问题呢?加锁。网站

3、多线程下的单例模式

多线程中,使用单例模式须要处理的就是如何避免多个线程建立多个实例的问题。能够采用锁机制来进行控制,代码以下:编码

 1 public class SingleTon
 2 {
 3     private static SingleTon _singleTon;
 4     private static readonly SingleTon _single = new SingleTon();
 5     private static readonly object _lock = new object();
 6     //私有构造,避免new操做符
 7     private SingleTon()
 8     {
 9 
10     }
11 
12     public static SingleTon GetSingleInstance()
13     {
14         if (_singleTon == null)
15         {
16             lock (_lock) 17             {
18                 if (_singleTon == null)
19                 {
20                     _singleTon = new SingleTon();
21                 }
22             }
23         }
24         return _singleTon;
25     }
26 
27     public static SingleTon GetSingleTonInstance()
28     {
29         return _single;
30     }
31 }

细心的你可能会发现这段代码和上面的代码相比,除了加了锁,还有一个变化,就是多了一层非空判断。下面我阐述下为何这样作,加锁很容易理解,就是避免多个线程同时进入if语句建立多个实例,试想一下若是不加第一层if非空判断会发生什么?假设有两个线程A和B,A和B线程同时访问GetSingleInstance方法,A先进入,因为加了锁,因此B必须等待A完成操做退出后才能够进入,如今A已经成功访问建立实例的方法,所以_singleTon此时不为空,B线程能够进入,发现_singleTon不为空了,直接退出。以上是A和B第一次访问,接着又来了两个线程C和D,一样是C先进入lock语句块,发现_singleTon此时不为空并退出,此时等待中的D能够进入了,和C发生了一样的遭遇。不知道你看出问题没有,既然第一次已经成功建立实例了,后续就没有必要再进入lock块了,由于不论你进或者不进入,被建立的实例就在那里。其实最外层的非空判断加与不加都不会影响单例的建立,因此最外层的非空判断核心做用就是提高性能,避免没有实际意义的操做。在个人另外一篇博文[设计模式应用之策略模式]中涉及到主题管理,负责主题管理的类就是一个单例模式。spa

4、懒汉式和饿汉式单例

懒汉模式就是第三部分的示例代码,在这里先把饿汉式单例的实现代码贴出来进行比较:线程

 1 public class SingleTon
 2 {
 3     private static readonly SingleTon _single = new SingleTon();
 4     //私有构造,避免new操做符
 5     private SingleTon()
 6     {
 7 
 8     }
 9 
10     public static SingleTon GetSingleTonInstance()
11     {
12         return _single;
13     }
14 }

饿汉式单例相对于懒汉式单例就简单多了,它直接在类的内部new一个SingleTon放在那,若是须要的时候就返回这个实例;反过来看一下懒汉式单例,它是在须要的时候,再去new一个SingleTon而后再返回这个实例。读到这也许你就会明白这两个名称的由来了,饿汉式单例是比较积极的,我知道你要来拿这个实例,我提早给你准备好,你来的时候,我就直接给你;而懒汉式则比较懒,不到最后我是不会建立实例的,你来的时候我现场建立一个再给你。设计

5、总结

单例模式是最为经常使用的设计模式之一,应用场景很是地广,因此使用的时候,须要注意一下几点:

  • 单例模式和核心就是程序的整个运行周期内只有很少于一个实例;
  • 私有化构造函数并建立一个公开的静态的全局访问点;
  • 若是是在多线程下使用,应该注意加锁,以及双重非空校验;
  • 理解懒汉式单例和饿汉式单例;

对于设计模式,不须要死记硬背,只要记住几个关键的点,而后在实际的编码中多进行应用,就能够掌握。

 

做者:悠扬的牧笛

博客地址:http://www.cnblogs.com/xhb-bky-blog/p/6251162.html

声明:本博客原创文字只表明本人工做中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未受权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文链接。

相关文章
相关标签/搜索