Fork me on GitHub

小菜学习设计模式(二)—单例(Singleton)模式

前言

设计模式目录:

本篇目录:

  单例模式(Singleton)可以说是最简单的模式,对.net来说,因为不需要考虑到垃圾回收机制,实现起来很简单,但是对于没有提供内存管理的平台来说,比如C++,因为单例模式只考虑创建对象,所以使用的时候要考虑全面些。

  其实说到些设计模式,我们有时候用到的真的很少,就像飞机零部件的模具不适用于汽车制造一样,某些设计模式也只在特定的环境下使用,单例模式的使用场景一般是资源管理器等,像说的最多的就是打印机场景:每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。说白点就是一个男人可以有很多女朋友,但是结婚生子的只能是其中一个。一夫多妻的情况就不是单例模式了,那应该是“多态”了。哈哈。

简单实现

  单例模式(Singleton)在.net中的定义是:一个类有且仅有一个实例,并且自行实例化向整个系统提供。

  从定义中我们可以看出,单例模式所具有的三个要点:

  • 某个类只能有一个实例
  • 必须自行创建这个实例
  • 必须自行向整个系统提供这个实例

  根据所说的要点,我们可以在.net中这样简单的实现:

 1     public class SingletonTest
 2     {
 3         public static SingletonTest model;
 4         private SingletonTest()
 5         { }
 6         public static SingletonTest getSingleton()
 7         {
 8             if (model==null)
 9             {
10                 model = new SingletonTest();
11             }
12             return model;
13         }
14     }

   代码就这么简单,在getSingleton()方法返回实例的时候要先判断对象是否已经被实例化,如果是就不需要重新创建了。

线程安全

  上面的代码看起来没什么问题,但是在多线程的情况下就会出现问题,我们来开几个线程测试下:

 1     public class SingletonTest
 2     {
 3         public static SingletonTest model;
 4         private SingletonTest()
 5         { }
 6         public static SingletonTest getSingleton()
 7         {
 8             if (model==null)
 9             {
10                 Console.WriteLine(String.Format("我是被线程:{0}创建的!", Thread.CurrentThread.Name));
11                 model = new SingletonTest();
12             }
13             return model;
14         }
15     }
 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Program p1 = new Program();
 6             p1.Test();
 7             Console.ReadLine();
 8         }
 9 
10         private void Test()
11         {
12             Thread newThread;
13             ThreadStart ts = new ThreadStart(DoWork);
14             for (int counter = 1; counter < 6; counter++)
15             {
16                 newThread = new Thread(ts);
17                 newThread.Name = "蟋蟀" + counter;
18                 newThread.Start();
19             }
20         }
21 
22         protected void DoWork()
23         {
24             //调用返回对象方法
25             SingletonTest.getSingleton();
26         }
27     }

  执行结果:

  根据上图的执行结果,会发现SingletonTest对象被实例化了2次,按照单例模式(Singleton)的特性:一个类只能有一个实例,那就不是单例模式了,为什么会实例化两次呢?因为我们的计算机执行速度很快,在某一个时间点,线程1在执行完if (model==null)这段代码,还没执行model = new SingletonTest(),线程2刚好执行判断对象是否null,就是说线程1和线程2都会进入下面的if判断体中实例化对象。

  关于单例模式的线程安全问题,网上一找一大堆,在《漫谈设计模式》这本书中,作者也提到了线程安全问题,java中是使用的是“Double-Check Locking”方法,还有序列化的问题,这边先不考虑,其实在.net中解决线程安全的问题也很简单,就是用lock锁,我们根据上面的代码,再来修改下,然后做个测试:

 1     public class SingletonTest
 2     {
 3         private static SingletonTest singleton;
 4         private static readonly object syncObject = new object();
 5         /// <summary>
 6         /// 构造函数必须是私有的
 7         /// 这样在外部便无法使用 new 来创建该类的实例
 8         /// </summary>
 9         private SingletonTest()
10         { }
11         /// <summary>
12         /// 定义一个全局访问点
13         /// 设置为静态方法
14         /// 则在类的外部便无需实例化就可以调用该方法
15         /// </summary>
16         /// <returns></returns>
17         public static SingletonTest getSingleton()
18         {
19             //这里可以保证只实例化一次
20             //即在第一次调用时实例化
21             //以后调用便不会再实例化
22             //第一重 singleton == null
23             if (singleton == null)
24             {
25                 lock (syncObject)
26                 {
27                     //第二重 singleton == null
28                     if (singleton == null)
29                     {
30                         Console.WriteLine(String.Format("我是被线程:{0}创建的!", Thread.CurrentThread.Name));
31                         singleton = new SingletonTest();
32                     }
33                 }
34             }
35             return singleton;
36         }
37     }

   执行结果:

  从上面的执行结果我们就可以看到,对象仅被实例化了一次,在某段代码体中,只能有且只有一个线程访问,加锁的目的就好比:我们去火车站售票大厅买票,因为买票的人太多,为了缓解压力,就多开了几个售票窗口(线程),比如南京到徐州的G110次列车只有一张票,窗口A和窗口B的人同时都在买这一班次的票,这时候就要加锁,不然就有可能会出现只有一张票,但是卖出去两张。话题跑偏了,哈哈。

  示例代码下载:Singleton.rar

后记

  关于模式,再多说两句,在某些情况下,像上面所说的:模式可以看成现实生活中的模具,有些产品(项目)是由一种模具(模式)生成出来的,比如杯子、瓶子等,有些产品(项目)是由多种模具(模式)生成出来,然后组合而成的,比如汽车、飞机等,是由成千上万个零部件组合形成的。就是说学会模式后要会懂得组合,而且要“合适”的组合,这样才会做出一个完善的产品(项目)。

  还是那就话:骚年们,和小菜一起整理学习吧,未完待续。。。

 

posted @ 2014-01-09 09:55  田园里的蟋蟀  阅读(2807)  评论(3编辑  收藏  举报