小菜学习设计模式(二)—单例(Singleton)模式
前言
设计模式目录:
- 小菜学习设计模式(一)—模板方法(Template)模式
- 小菜学习设计模式(二)—单例(Singleton)模式
- 小菜学习设计模式(三)—工厂方法(Factory Method)模式
- 小菜学习设计模式(四)—原型(Prototype)模式
- 小菜学习设计模式(五)—控制反转(Ioc)
- 持续更新中。。。
本篇目录:
单例模式(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
后记
关于模式,再多说两句,在某些情况下,像上面所说的:模式可以看成现实生活中的模具,有些产品(项目)是由一种模具(模式)生成出来的,比如杯子、瓶子等,有些产品(项目)是由多种模具(模式)生成出来,然后组合而成的,比如汽车、飞机等,是由成千上万个零部件组合形成的。就是说学会模式后要会懂得组合,而且要“合适”的组合,这样才会做出一个完善的产品(项目)。
还是那就话:骚年们,和小菜一起整理学习吧,未完待续。。。
微信公众号:你好架构
出处:http://www.cnblogs.com/xishuai/
公众号会不定时的分享有关架构的方方面面,包含并不局限于:Microservices(微服务)、Service Mesh(服务网格)、DDD/TDD、Spring Cloud、Dubbo、Service Fabric、Linkerd、Envoy、Istio、Conduit、Kubernetes、Docker、MacOS/Linux、Java、.NET Core/ASP.NET Core、Redis、RabbitMQ、MongoDB、GitLab、CI/CD(持续集成/持续部署)、DevOps等等。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。