通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance)  的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例。Windows系统的系统任务管理器也是始终只有一个,如果你打开了windows管理器,你再想打开一个那么他还是同一个界面(同一个实例), 还有比如 做.Net平台的人都知道,AppDomain 对象,一个系统中也只有一个,所有的类库都会加载到AppDomain中去运行。只需要一个实例对象的场景,随处可见,那么有么有什么好的解决方法来应对呢? 有的,那就是 单例模式。

一、单例模式定义

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

二、单例模式结构图

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

三、 单例模式典型代码

  1. public class Singleton
  2. {
  3. private static Singleton instance;
  4. private Singleton()
  5. {
  6. }
  7. public static Singleton GetInstance()
  8. {
  9. if(instance==null)
  10. {
  11. instance=new Singleton();
  12. }
  13.  
  14. return instance;
  15. }
  16. }

客户端调用代码:

  1. static void Main(string[] args)
  2. {
  3. Singleton singleto = Singleton.GetInstance();
  4. }

在C#中经常将统一访问点暴露出一个只读的属性供客户端程序使用,这样代码就变成了这样:

  1. public class Singleton
  2. {
  3. private static Singleton instance;
  4. private Singleton()
  5. {
  6. }
  7. public static Singleton GetInstance
  8. {
  9. get
  10. {
  11. if (instance == null)
  12. {
  13. instance = new Singleton();
  14. }
  15.  
  16. return instance;
  17. }
  18. }
  19. }

客户端调用:

  1. static void Main(string[] args)
  2. {
  3. Singleton singleton = Singleton.GetInstance;
  4. }

四、单例模式实例

1. 懒汉模式

假如我们要做一个程序计数器,一旦程序启动无论多少个客户端调用这个 计数器计数的结果始终都是在前一个的基础上加1,那么这个计数器类就可以设计成一个单例模式的类。

  1. public class SingletonCounter
  2. {
  3. private static SingletonCounter instance;
  4. private static int number=0;
  5. private SingletonCounter() { }
  6. public static SingletonCounter Instance
  7. {
  8. get
  9. {
  10. if (instance == null) instance = new SingletonCounter();
  11.  
  12. number++;
  13. return instance;
  14. }
  15. }
  16.  
  17. public int GetCounter(){
  18. return number;
  19. }
  20. }

客户端调用:

  1. static void Main(string[] args)
  2. {
  3. //App A call the counter;
  4. SingletonCounter singletonA = SingletonCounter.Instance;
  5. int numberA = singletonA.GetCounter();
  6. Console.WriteLine("App A call the counter get number was:" + numberA);
  7.  
  8. //App B call the counter;
  9. SingletonCounter singletonB = SingletonCounter.Instance;
  10. int numberB = singletonA.GetCounter();
  11. Console.WriteLine("App B call the counter get number was:" + numberB);
  12.  
  13. Console.ReadKey();
  14. }

输出结果:

这个实现是线程不安全的,如果有多个线程同时调用,并且又恰恰在计数器初始化的瞬间多个线程同时检测到了 instance==null为true情况,会怎样呢?这就是下面要讨论的 “加锁懒汉模式”

2、加锁懒汉模式

多个线程同时调用并且同时检测到 instance == null 为 true的情况,那后果就是会出现多个实例了,那么就无法保证唯一实例了,解决这个问题就是增加一个对象锁来确保在创建的过程中只有一个实例。(锁可以确保锁住的代码块是线程独占访问的,如果一个线程占有了这个锁,其它线程只能等待该线程释放锁以后才能继续访问)。

  1. public class SingletonCounter
  2. {
  3. private static SingletonCounter instance;
  4. private static readonly object locker = new object();
  5. private static int number = 0;
  6. private SingletonCounter() { }
  7. public static SingletonCounter Instance
  8. {
  9. get
  10. {
  11. lock (locker)
  12. {
  13. if (instance == null) instance = new SingletonCounter();
  14.  
  15. number++;
  16. return instance;
  17. }
  18. }
  19. }
  20.  
  21. public int GetCounter()
  22. {
  23. return number;
  24. }
  25. }

客户端调用代码:

  1. static void Main(string[] args)
  2. {
  3. for (int i = 1; i < 100; i++)
  4. {
  5. var task = new Task(() =>
  6. {
  7. SingletonCounter singleton = SingletonCounter.Instance;
  8. int number = singleton.GetCounter();
  9.  
  10. Console.WriteLine("App call the counter get number was:" + number);
  11.  
  12. });
  13. task.Start();
  14. }
  15. Console.ReadKey();
  16. }

输出结果:

这种模式是线程安全,即使在多线程的情况下仍然可以保持单个实例。那么这种模式会不会有什么问题呢?假如系统的访问量非常大,并发非常高,那么计数器就会是一个性能瓶颈,因为对锁会使其它的线程无法访问。在访问量不大,并发量不高的系统尚可应付,如果高访问量,高并发的情况下这样做肯定是不行的,那么有什么办法改进呢?这就是下面要讨论的“双检查加锁懒汉模式”。

3、双检查加锁懒汉模式

加锁懒汉模式虽然保证了系统的线程安全,但是却为系统带来了新能问题,主要的性能来自锁带来开销,双检查就是解决这个锁带来的问题,在锁之前再做一次 instance==null的检查,如果返回true就直接返回 单例对象了,避开了无谓的锁, 我们来看下,双检查懒汉模式代码:

  1. public class DoubleCheckLockSingletonCounter
  2. {
  3. private static DoubleCheckLockSingletonCounter instance;
  4. private static readonly object locker = new object();
  5. private static int number = 0;
  6. private DoubleCheckLockSingletonCounter() { }
  7. public static DoubleCheckLockSingletonCounter Instance
  8. {
  9. get
  10. {
  11. if (instance == null)
  12. {
  13. lock (locker)
  14. {
  15. if (instance == null)
  16. {
  17. instance = new DoubleCheckLockSingletonCounter();
  18. }
  19. }
  20. }
  21. number++;
  22. return instance;
  23. }
  24. }
  25.  
  26. public int GetCounter()
  27. {
  28. return number;
  29. }
  30. }

客户端调用代码和“懒汉加锁模式”相同,输出结果也相同。

4、饿汉模式

单例模式除了我们上面讲的三种懒汉模式外,还有一种叫“饿汉模式”的实现方式,“饿汉模式”直接在Singleton类里实例化了当前类的实例,并且保存在一个静态对象中,因为是静态对象,所以在程序启动的时候就已经实例化好了,后面直接使用,因此不存在线程安全的问题。

下面是“饿汉模式”的代码实现:

  1. public class EagerSingletonCounter
  2. {
  3. private static EagerSingletonCounter instance = new EagerSingletonCounter();
  4.  
  5. private static int number = 0;
  6. private EagerSingletonCounter() { }
  7. public static EagerSingletonCounter Instance
  8. {
  9. get
  10. {
  11. number++;
  12. return instance;
  13. }
  14. }
  15.  
  16. public int GetCounter()
  17. {
  18. return number;
  19. }
  20. }

五、单例模式应用场景

单例模式只有一个角色非常简单,使用的场景也很明确,就是一个类只需要、且只能需要一个实例的时候使用单例模式。

六、扩展

1、”饿汉模式“和”懒汉模式“的比较

”饿汉模式“在程序启动的时候就已经实例化好了,并且一直驻留在系统中,客户程序调用非常快,因为它是静态变量,虽然完美的保证线程的安全,但是如果创建对象的过程很复杂,要占领系统或者网络的一些昂贵的资源,但是在系统中使用的频率又极低,甚至系统运行起来后都不会去使用该功能,那么这样一来,启动之后就一直占领着系统的资源不释放,这有些得不偿失。

“懒汉模式“ 恰好解决了”饿汉模式“这种占用资源的问题,”懒汉模式”将类的实例化延迟到了运行时,在使用时的第一次调用时才创建出来并一直驻留在系统中,但是为了解决线程安全问题, 使用对象锁也是 影响了系统的性能。这两种模式各有各的好处,但是又各有其缺点。

有没有一种折中的方法既可以避免一开始就实例化且一直占领系统资源,又没有性能问题的Singleton呢? 答案是:有的。

2、第三种选择

“饿汉模式“类不能实现延迟加载,不管用不用始终占据内存;”懒汉式模式“类线程安全控制烦琐,而且性能受影响。我们用一种折中的方法来解决这个问题,针对主要矛盾, 即:既可以延时加载又不影响性能。

在Singleton的内部创建一个私有的静态类用于充当单例类的”初始化器“,专门用来创建Singleton的实例:

  1. public class BestPracticeSingletonCounter
  2. {
  3. private static class SingletonInitializer{
  4. public static BestPracticeSingletonCounter instance = new BestPracticeSingletonCounter();
  5. }
  6.  
  7. private static int number = 0;
  8. private BestPracticeSingletonCounter() { }
  9. public static BestPracticeSingletonCounter Instance
  10. {
  11. get
  12. {
  13. number++;
  14. return SingletonInitializer.instance;
  15. }
  16. }
  17.  
  18. public int GetCounter()
  19. {
  20. return number;
  21. }
  22. }

这种模式兼具了”饿汉“和”懒汉“模式的优点有摒弃了其缺点,可以说是一个完美的实现。

【设计模式】单例模式 Singleton Pattern的更多相关文章

  1. 浅谈设计模式--单例模式(Singleton Pattern)

    题外话:好久没写blog,做知识归纳整理了.本来设计模式就是个坑,各种文章也写烂了.不过,不是自己写的东西,缺少点知识的存在感.目前还没做到光看即能记住,得写.所以准备跳入设计模式这个大坑. 开篇先贡 ...

  2. 设计模式之单例模式(Singleton Pattern)

    单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...

  3. 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)

    原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...

  4. 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性

    模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...

  5. 二十四种设计模式:单例模式(Singleton Pattern)

    单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using S ...

  6. 抽象工厂(Abstract Factory),工厂方法(Factory Method),单例模式(Singleton Pattern)

    在谈工厂之前,先阐述一个观点:那就是在实际程序设计中,为了设计灵活的多态代码,代码中尽量不使用new去实例化一个对象,那么不使用new去实例化对象,剩下可用的方法就可以选择使用工厂方法,原型复制等去实 ...

  7. 十次艳遇单例设计模式(Singleton Pattern)

    1.引言 单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访 ...

  8. 设计模式 单例模式(Singleton) [ 转载2 ]

    设计模式 单例模式(Singleton) [ 转载2 ] @author java_my_life 单例模式的结构 单例模式的特点: 单例类只能有一个实例. 单例类必须自己创建自己的唯一实例. 单例类 ...

  9. 设计模式 单例模式(Singleton) [ 转载 ]

    设计模式 单例模式(Singleton) [ 转载 ] 转载请注明出处:http://cantellow.iteye.com/blog/838473 前言 懒汉:调用时才创建对象 饿汉:类初始化时就创 ...

随机推荐

  1. No module named MySQLdb

    解决办法 easy_install mysql-python (mix os) pip install mysql-python (mix os/ python 2) pip install mysq ...

  2. ASP.NET Core - 利用Windsor Castle实现通用注册

    问题引入 在ASP.NET Core - 依赖注入这篇文章里面,我们知道了如何利用ASP.NET Core原生的容器来实现依赖注入的,那我们为什么要替换掉默认的 IoC容器呢?从ASP.NET Cor ...

  3. Kubernetes集群部署关键知识总结

    Kubernetes集群部署需要安装的组件东西很多,过程复杂,对服务器环境要求很苛刻,最好是能连外网的环境下安装,有些组件还需要连google服务器下载,这一点一般很难满足,因此最好是能提前下载好准备 ...

  4. 如何在ASP.NET Core中自定义Azure Storage File Provider

    文章标题:如何在ASP.NET Core中自定义Azure Storage File Provider 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p ...

  5. .Net Core ORM选择之路,哪个才适合你

    因为老板的一句话公司项目需要迁移到.Net Core ,但是以前同事用的ORM不支持.Net Core 开发过程也遇到了各种坑,插入条数多了也特别的慢,导致系统体验比较差好多都改写Sql实现. 所以我 ...

  6. spring Boot环境下dubbo+zookeeper的一个基础讲解与示例

    一,学习背景 1.   前言 对于我们不管工作还是生活中,需要或者想去学习一些东西的时候,大致都考虑几点: a)      我们为什么需要学习这个东西? b)     这个东西是什么? c)      ...

  7. 记录SoapUI使用说明

    一.SoapUI简介 SoapUI是一个开源测试工具,通过soap/http来检查.调用.实现Web Service的功能/负载/符合性测试.该工具既可作为一个单独的测试软件使用,也可利用插件集成到E ...

  8. 微信小程序 base64 图片 canvas 画布 drawImage 实现

    在微信小程序中 canvas drawImage API 传入的第一个参数是 imageResource 图片资源路径,这个参数通常由从相册选择图片 wx.chooseImage 或 wx.getIm ...

  9. 基于python语言的tensorflow的‘端到端’的字符型验证码识别源码整理(github源码分享)

    基于python语言的tensorflow的‘端到端’的字符型验证码识别 1   Abstract 验证码(CAPTCHA)的诞生本身是为了自动区分 自然人 和 机器人 的一套公开方法, 但是近几年的 ...

  10. MQTT 单个订阅消息量过大处理

    The missing piece between MQTT and a SQL database in a M2M landscape Message Queue Telemetry Transpo ...