作者:周旭龙 出处:http://edisonchou.cnblogs.com 设计模式的征途—1.单例(Singleton)模式

  单例模式属于创建型模式的一种,创建型模式是一类最常用的设计模式,在软件开发中应用非常广泛。创建型模式将对象的创建和使用分离,在使用对象时无需关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。每一个创建型模式都在视图回答3个问题:3W -> 创建什么(What)、由谁创建(Who)和何时创建(When)。

  本篇是创建型模式的第一篇,也是最简单的一个设计模式,虽然简单,但是其使用频率确是很高的。

单例模式(Singleton) 学习难度:★☆☆☆☆ 使用频率:★★★★☆

一、单例模式的动机

  相信大家都使用过Windows任务管理器,我们可以做一个尝试:在Windows任务栏的右键菜单上多次点击“启动任务管理器”,看能否打开多个任务管理器窗口。正常情况下,无论我们启动多少次,Windows系统始终只能弹出一个任务管理器窗口。也就是说,在一个Windows系统中,任务管理器存在唯一性。

  在实际开发中,我们经常也会遇到类似的情况,为了节约系统资源,有时候需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,可以通过创建单例模式来实现,这也就是单例模式的动机所在。

二、单例模式概述

2.1 要点

单例(Singleton)模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建模式。

  单例模式有3个要点:

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

2.2 结构图

  从上图中可以看出,单例模式结构图中只包含了一个单例的角色。

  Singleton(单例):

  • 在单例类的内部实现只生成一个实例,同时它提供一个静态的GetInstance()方法,让客户可以访问它的唯一实例;
  • 为了防止在外部对单例类实例化,它的构造函数被设为private;
  • 在单例类的内部定义了一个Singleton类型的静态对象,作为提供外部共享的唯一实例。

三、负载均衡器的设计

3.1 软件需求

  假设M公司成都分公司的IT开发部门承接了一个服务器负载均衡器(Load Balance)软件的开发,该软件运行在一台负载均衡服务器上面,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高系统的整体处理能力,缩短响应时间。由于集群中的服务器需要动态增减,且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性,即只能有一个负载均衡器实例来管理服务器和分发请求,否则会带来服务器状态的不一致以及请求的分配冲突等问题。

  如何确保负载均衡器的唯一性成为了这个软件成功地关键。

3.2 撸起袖子加油干

  成都分公司的开发人员通过分析和权衡,决定使用单例模式来设计这个负载均衡器,于是撸起袖子画了一个结构图如下:

  在上图所示的UML图中,将LoadBalancer类设计为了单例类,其中包含了一个存储服务器信息的集合serverList,每次在serverList中随机选择一台服务器来响应客户端的请求,其实现代码如下:

    /// <summary>/// 假装自己是一个负载均衡器/// </summary>public class LoadBalancer{// 私有静态变量,存储唯一实例private static LoadBalancer instance = null;// 服务器集合private IList<CustomServer> serverList = null;
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 私有构造函数</span><span style="color: #0000ff;">private</span><span style="color: #000000;"> LoadBalancer(){serverList </span>= <span style="color: #0000ff;">new</span> List&lt;CustomServer&gt;<span style="color: #000000;">();}</span><span style="color: #008000;">//</span><span style="color: #008000;"> 公共静态成员方法,返回唯一实例</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> LoadBalancer GetLoadBalancer(){</span><span style="color: #0000ff;">if</span> (instance == <span style="color: #0000ff;">null</span><span style="color: #000000;">){instance </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LoadBalancer();}</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;}</span><span style="color: #008000;">//</span><span style="color: #008000;"> 添加一台Server</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> AddServer(CustomServer server){serverList.Add(server);}</span><span style="color: #008000;">//</span><span style="color: #008000;"> 移除一台Server</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> RemoveServer(<span style="color: #0000ff;">string</span><span style="color: #000000;"> serverName){</span><span style="color: #0000ff;">foreach</span> (<span style="color: #0000ff;">var</span> server <span style="color: #0000ff;">in</span><span style="color: #000000;"> serverList){</span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (server.Name.Equals(serverName)){serverList.Remove(server);</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;}}}</span><span style="color: #008000;">//</span><span style="color: #008000;"> 获得一台Server - 使用随机数获取</span><span style="color: #0000ff;">private</span> Random rand = <span style="color: #0000ff;">new</span><span style="color: #000000;"> Random();</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> CustomServer GetServer(){</span><span style="color: #0000ff;">int</span> index =<span style="color: #000000;"> rand.Next(serverList.Count);</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> serverList[index];}
}</span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;summary&gt;</span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 假装自己是一台服务器
</span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;/summary&gt;</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> CustomServer
{</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">string</span> Name { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">int</span> Size { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
}</span></pre>

  现在我们在客户端代码中添加一些测试代码,看看结果:

    public class Program{public static void Main(string[] args){LoadBalancer balancer, balancer2, balancer3;balancer = LoadBalancer.GetLoadBalancer();balancer2 = LoadBalancer.GetLoadBalancer();balancer3 = LoadBalancer.GetLoadBalancer();
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 判断负载均衡器是否相同</span><span style="color: #0000ff;">if</span> (balancer == balancer2 &amp;&amp; balancer == balancer3 &amp;&amp; balancer2 ==<span style="color: #000000;"> balancer3){Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">^_^ : 服务器负载均衡器是唯一的!</span><span style="color: #800000;">"</span><span style="color: #000000;">);}</span><span style="color: #008000;">//</span><span style="color: #008000;"> 增加服务器</span>balancer.AddServer(<span style="color: #0000ff;">new</span> CustomServer() { Name = <span style="color: #800000;">"</span><span style="color: #800000;">Server 1</span><span style="color: #800000;">"</span><span style="color: #000000;"> });balancer.AddServer(</span><span style="color: #0000ff;">new</span> CustomServer() { Name = <span style="color: #800000;">"</span><span style="color: #800000;">Server 2</span><span style="color: #800000;">"</span><span style="color: #000000;"> });balancer.AddServer(</span><span style="color: #0000ff;">new</span> CustomServer() { Name = <span style="color: #800000;">"</span><span style="color: #800000;">Server 3</span><span style="color: #800000;">"</span><span style="color: #000000;"> });balancer.AddServer(</span><span style="color: #0000ff;">new</span> CustomServer() { Name = <span style="color: #800000;">"</span><span style="color: #800000;">Server 4</span><span style="color: #800000;">"</span><span style="color: #000000;"> });</span><span style="color: #008000;">//</span><span style="color: #008000;"> 模拟客户端请求的分发</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = <span style="color: #800080;">0</span>; i &lt; <span style="color: #800080;">10</span>; i++<span style="color: #000000;">){CustomServer server </span>=<span style="color: #000000;"> balancer.GetServer();Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">该请求已分配至 : </span><span style="color: #800000;">"</span> +<span style="color: #000000;"> server.Name);}Console.ReadKey();}
}</span></pre>

  运行客户端代码,查看运行结果:

  从运行结果中我们可以看出,虽然我们创建3个LoadBalancer对象,但是它们实际上是同一个对象。因此,通过使用单例模式可以确保LoadBalancer对象的唯一性。

3.3 饿汉式与懒汉式单例

  在进行测试时,成都分公司的测试人员发现负载均衡器在启动过程中用户再次启动负载均衡器时,系统无任何异常,但当客户端提交请求时出现请求分发失败,通过仔细分析发现原来系统中还是会存在多个负载均衡器的对象,从而导致分发时目标服务器不一致,从而产生冲突。

  开发部人员对实现代码进行再一次分析,当第一次调用GetLoadBalancer()方法创建并启动负载均衡器时,instance对象为null,因此系统将会实例化其对象,在此过程中,由于要对LoadBalancer进行大量初始化工作,需要一段时间来创建LoadBalancer对象。而在此时,如果再一次调用GetLoadBalancer()方法(通常发生在多线程环境中),由于instance尚未创建成功,仍为null值,于是会再次实例化LoadBalancer对象,最终导致创建了多个instance对象,这也就违背了单例模式的初衷,导致系统发生运行错误。

  So,如何解决这个问题?也就有了下面的饿汉式与懒汉式的解决方案。

 (1)饿汉式单例 

  懒汉式单例实现起来最为简单,在C#中,我们可以利用静态构造函数来实现。于是我们可以改写以上的代码块:

    public class LoadBalancer{// 私有静态变量,存储唯一实例private static readonly LoadBalancer instance = new LoadBalancer();
    ......</span><span style="color: #008000;">//</span><span style="color: #008000;"> 公共静态成员方法,返回唯一实例</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> LoadBalancer GetLoadBalancer(){</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;}
}</span></pre>

C#的语法中有一个函数能够确保只调用一次,那就是静态构造函数。由于C#是在调用静态构造函数时初始化静态变量,.NET运行时(CLR)能够确保只调用一次静态构造函数,这样我们就能够保证只初始化一次instance。

  饿汉式是在 .NET 中实现 Singleton 的首选方法。但是,由于在C#中调用静态构造函数的时机不是由程序员掌控的,而是当.NET运行时发现第一次使用该类型的时候自动调用该类型的静态构造函数(也就是说在用到LoadBalancer时就会被创建,而不是用到LoadBalancer.GetLoadBalancer()时),这样会过早地创建实例,从而降低内存的使用效率。此外,静态构造函数由 .NET Framework 负责执行初始化,我们对对实例化机制的控制权也相对较少。

 (2)懒汉式单例

  除了饿汉式之外,还有一种懒汉式。最开始我们实现的方式就是一种懒汉式单例,也就是说,在第一个调用LoadBalancer.GetLoadBalancer()时才会实例化对象,这种技术又被称之为延迟加载(Lazy Load)。同样,我们的目标还是为了避免多个线程同时调用GetLoadBalancer方法,在C#中,我们可以使用关键字lock/Moniter.Enter+Exit等来实现,这里采用关键字语法糖lock来改写代码段:

    public class LoadBalancer{// 私有静态变量,存储唯一实例private static LoadBalancer instance = null;private static readonly object syncLocker = new object();
    ......</span><span style="color: #008000;">//</span><span style="color: #008000;"> 公共静态成员方法,返回唯一实例</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> LoadBalancer GetLoadBalancer(){</span><span style="color: #0000ff;">if</span> (instance == <span style="color: #0000ff;">null</span><span style="color: #000000;">){</span><span style="color: #0000ff;">lock</span><span style="color: #000000;"> (syncLocker){instance </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LoadBalancer();}}</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;}
}    </span></pre>

  问题貌似得以解决,但事实并非如此。如果使用以上代码来创建单例对象,还是会存在单例对象不一致。假设线程A先进入lock代码块内,执行实例化代码。此时线程B排队吃瓜等待,必须等待线程A执行完毕后才能进入lock代码块。但当A执行完毕时,线程B并不知道实例已经被创建,将继续创建新的实例,从而导致多个单例对象。因此,开发人员需要进一步改进,于是就有了双重检查锁定(Double-Check Locking),其改写代码如下:

    public class LoadBalancer{// 私有静态变量,存储唯一实例private static LoadBalancer instance = null;private static readonly object syncLocker = new object();
    ......</span><span style="color: #008000;">//</span><span style="color: #008000;"> 公共静态成员方法,返回唯一实例</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> LoadBalancer GetLoadBalancer(){</span><span style="color: #008000;">//</span><span style="color: #008000;"> 第一重判断</span><span style="color: #0000ff;">if</span> (instance == <span style="color: #0000ff;">null</span><span style="color: #000000;">){</span><span style="color: #008000;">//</span><span style="color: #008000;"> 锁定代码块</span><span style="color: #0000ff;">lock</span><span style="color: #000000;"> (syncLocker){</span><span style="color: #008000;">//</span><span style="color: #008000;"> 第二重判断</span><span style="color: #0000ff;">if</span> (instance == <span style="color: #0000ff;">null</span><span style="color: #000000;">){instance </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LoadBalancer();}}}</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;}
}</span></pre>

 (3)一种更好的单例实现

  饿汉式单例不能延迟加载,懒汉式单例安全控制繁琐,而且性能受影响。静态内部类单例则将这两者有点合二为一。使用这种方式,我们需要在单例类中增加一个静态内部类,在该内部类中创建单例对象,再将该单例对象通过GetInstance()方法返回给外部使用,于是开发人员又改写了代码:

    public class LoadBalancer{......
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> 公共静态成员方法,返回唯一实例</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> LoadBalancer GetLoadBalancer(){</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> Nested.instance;}</span><span style="color: #008000;">//</span><span style="color: #008000;"> 使用内部类+静态构造函数实现延迟初始化</span><span style="color: #0000ff;">class</span><span style="color: #000000;"> Nested{</span><span style="color: #0000ff;">static</span><span style="color: #000000;"> Nested() { }</span><span style="color: #0000ff;">internal</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">readonly</span> LoadBalancer instance = <span style="color: #0000ff;">new</span><span style="color: #000000;"> LoadBalancer();}......
}</span></pre>

  该实现方法在内部定义了一个私有类型Nested。当第一次用到这个嵌套类型的时候,会调用静态构造函数创建LoadBalancer的实例instance。如果我们不调用属性LoadBalancer.GetLoadBalancer()

,那么就不会触发.NET运行时(CLR)调用Nested,也就不会创建实例,因此也就保证了按需创建实例(或延迟初始化)。

  可见,此方法既可以实现延迟加载,又可以保证线程安全,不影响系统性能。但其缺点是与具体编程语言本身的特性相关,有一些面向对象的编程语言并不支持此种方式。

四、单例模式总结

  单例模式目标明确,结构简单,在软件开发中使用频率相当高。

4.1 主要优点

  (1)提供了对唯一实例的受控访问。单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。

  (2)由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。

  (3)允许可变数目的示例。基于单例模式,开发人员可以进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省系统资源,又解决了单例对象共享过多有损性能的问题。(Note:自行提供指定书目的实例对象的类可称之为多例类)例如,数据库连接池,线程池,各种池。

4.2 主要缺点

  (1)单例模式中没有抽象层,因此单例类的扩展有很大的困难。

  (2)单例类的职责过重,在一定程度上违背了单一职责的原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起。不够,很多时候我们都需要取得平衡。

  (3)很多高级面向对象编程语言如C#和Java等都提供了垃圾回收机制,如果实例化的共享对象长时间不被利用,系统则会认为它是垃圾,于是会自动销毁并回收资源,下次利用时又得重新实例化,这将导致共享的单例对象状态的丢失。

4.3 适用场景

  (1)系统只需要一个实例对象。例如:系统要求提供一个唯一的序列号生成器或者资源管理器,又或者需要考虑资源消耗太大而只允许创建一个对象。

  (2)客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

  比如,在Flappy Bird游戏中,小鸟这个游戏对象在整个游戏中应该只存在一个实例,所有对于这个小鸟的操作(向上飞、向下掉等)都应该只会针对唯一的一个实例进行。

参考资料

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

  何海涛,《剑指Offer—名企面试官精讲典型编程题》(题目1-实现Singleton模式)

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

1.单例(Singleton)模式相关推荐

  1. 设计模式学习笔记——单例(Singleton)模式

    设计模式学习笔记--单例(Singleton)模式 @(设计模式)[设计模式, 单例模式, Singleton, 懒汉式, 饿汉式] 设计模式学习笔记单例Singleton模式 基本介绍 单例案例 类 ...

  2. 设计模式--单例(Singleton)模式

    模式意图 保证一个类只用一个实例,并且提供一个全局访问点 类图 应用场景 1.需要更严格地控制全局变量时,使用单例模式: 2.重量级的对象如线程池对象,数据库连接池对象,不需要多个实例的对象如工具类等 ...

  3. 设计一个线程安全的单例(Singleton)模式

    在设计单例模式的时候.尽管非常easy设计出符合单例模式原则的类类型,可是考虑到垃圾回收机制以及线程安全性.须要我们思考的很多其它.有些设计尽管能够勉强满足项目要求,可是在进行多线程设计的时候.不考虑 ...

  4. 设计模式C++描述----01.单例(Singleton)模式

    一.概念 单例模式:其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享. class CSingleton { //公有的静态方法,来获取该实例 public: s ...

  5. 单例/单体模式(Singleton)

    单例/单体模式(Singleton) 首先,单例模式是对象的创建模式之一,此外还包括工厂模式.单例模式的三个特点: 1,该类只有一个实例 2,该类自行创建该实例(在该类内部创建自身的实例对象) 3,向 ...

  6. 单例测试模式中【饿汉式】与【懒汉式】的区别

    package day25.thread;/** /*** @author Mr Chen* @create 2018-10-09 18:37* 单例测试模式:保证类在内存中只有一个对象*/ publ ...

  7. 跨应用程序域(AppDomain)的单例(Singleton)实现

    转载自: 跨应用程序域(AppDomain)的单例(Singleton)实现 - CorePlex代码库 - CorePlex官方网站,Visual Studio插件,代码大全,代码仓库,代码整理,分 ...

  8. Ruby设计模式透析之 —— 单例(Singleton)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/8868758 此为Java设计模式透析的拷贝版,专门为Ruby爱好者提供的,不熟悉R ...

  9. 我心中的核心组件(可插拔的AOP)~第十五回 我的日志组件Logger.Core(策略,模版方法,工厂,单例等模式的使用)...

    回到目录 之前的讲过两篇关于日志组件的文章,分别是<第一回  日志记录组件之自主的Vlog>和<第三回  日志记录组件之log4net>,而今天主要说一下我自己开发的另一种日志 ...

  10. 单例Singleton

    先提供一个完整版: // .h文件 @interface SingleTon : NSObject /** 获取单例对象*/ + (instancetype)sharedInstance; + (in ...

最新文章

  1. java反射 获取参数名_java
  2. nginx配置文件基本配置
  3. 熟练掌握Word2003中的突出显示功能
  4. javaweb利用servlet与struts2实现可点击刷新的基础图片验证码
  5. Psych101(part4)--Day4
  6. 通俗彻底解读批处理的延迟变量
  7. python爬虫 入门+进阶_python爬虫入门到进阶(三)
  8. 看看虚函数表是什么样的
  9. 【Animations】使用弹簧物理学动画运动(8)
  10. C++中const与指针、引用的分析(转自china_unix GP-King)
  11. python dataframe去除重复项_python - Pandas DataFrame处理查找DataFrame中的重复项 - 堆栈内存溢出...
  12. apache如何加载系统环境变量_游学电子:windows10系统如何用cmd指令设置环境变量...
  13. Proteus仿真:存储器
  14. 安防监控系统百问百答
  15. 富士急乐园免税店将开业,打造游园购物访日体验
  16. AT32F407/437 LWIP FreeRTOS Multicast
  17. Codewar刷题总结
  18. python爬取喜马拉雅FM音频
  19. Matlab sim函数的用法
  20. MXT6208量产修复工具+v2.0非常好用哦!

热门文章

  1. java 软件流程图使用什么_流程图怎么画,教你正确使用流程图模板
  2. echo和narcissus寓意_希腊神话故事(一)Echo 和 Narcissus
  3. 淘宝定价的方式有什么,如何根据活动来定价
  4. HTML的head,头头头头!!!
  5. 信签纸有虚线怎么写_信签纸写作文格式怎么用
  6. 腾讯公布员工数据:超 30 岁员工占近六成
  7. 老徐WEB:js入门学习 - 认识javascript
  8. hdu5285-wyh2000 and pupil-(染色法二分图判定)
  9. 计算机启动时检测硬盘,电脑总是启动检测硬盘怎么办
  10. html页面导出word文档