在最近的一个项目里面发现好多同事喜欢这样运用单例模式,样例代码如下

public class Demo
{
  public static Demo Instance{
    get { return new Demo(); }}  public string GetUserId(){
    return "001";}  public string GetUserName(){
    return "tauruswu";}
}

在调用这个类的时候,是这样操作的

var id = Demo.Instance.GetUserId();
var name = Demo.Instance.GetUserName();

粗略一看,可能觉得没有问题,最开始我也是这样,看别人都这么写,我也就这么写,其实这个时候你的直觉已经明显的欺骗你了,各位看官再仔细看看Demo类里面的静态属性Instance以及我们调用的方式,有没有看出什么端倪来?

  很显然,上面的调用方法已经违背了单例模式的宗旨,或者可以说是披着单例模式的外衣,却不做单例模式该做的事情。单例模式的解释是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。那么我们应该如何正确的使用单例模式了?

  何为单例模式

  再回头看上面解释单例模式的话,第一句话说“保证一个类仅有一个实例”,好,那么我们怎样能够保证一个类仅有一个实例了,幸好在C#里面,提供了私有构造器,我们在创建一个类的时候,往往会在类的构造函数里面初始化一些对象,这里的构造器是公开的,如下面

public Demo(){// to do }

那么很显然,私有构造器就是private的,如下面

private Demo(){// to do }

一旦有了私有构造器,那么这个类就会阻止类外面的代码创建实例,不相信我们就来尝试一下。还是用上面的Demo类

public class Demo
{
  private Demo(){ // to do }
}

然后再外面去实例化它,看截图

 这样一来,你应该明白了私有构造器的作用了吧。

  解释单例模式的前半句话上面说的很清楚了,然后在看看后半句“并提供一个访问它的全局访问点”,也就是说向外部提供访问该实例的方法或者属性,怎么写?我们将开篇的例子稍作修改

public class Demo
{private Demo(){
    // to do
  }  private static Demo _instance;  public static Demo Instance{
    get{if (_instance == null)      {        Console.WriteLine(string.Format("线程{0}在{1}时刻发现Instance为null", Thread.CurrentThread.Name,DateTime.Now));_instance = new Demo();      }return _instance;}}
}

调用方式与开篇的一样,这个时候你在单步调试进去,看看发现了什么。到这里,我们因该能正确的理解单例模式以及如何使用单例模式了。

  多线程环境下莫名其妙的错误

  上面的例子在单线程环境下可以正常的运转,如果换做是多线程环境下,它还能正确的运转吗?

  我们来做这样一个实验:1. 在一个程序启动时创建两个线程,线程A与线程B

             2. 线程A与线程B分别调用Demo类

如果仅凭自觉的话,我们肯定会觉得只有一个线程来创建Demo的实例,那么事实是不是这样了?Demo类还是上面的那个类,未作任何修改。然后在另一个类中启动两个线程,分别调用类Demo

public class Invoke{public void Run(){Thread t1 = new Thread(new ThreadStart(fun1));t1.Name = "AAA";t1.Start();Thread t2 = new Thread(new ThreadStart(fun2));t2.Name = "BBB";t2.Start();}private void fun1(){while (true){Demo1.Instance.GetUserId();Thread.Sleep(1);}}private void fun2(){while (true){Demo1.Instance.GetUserId();Thread.Sleep(1);}}}

最后我们在控制台程序里面运行调用类Singleton,看效果图

哥,你目瞪口呆了吧?怎么会有这样的结果?事实证明上面的写法在多线程环境里面会出问题的,那么我们该怎么样去修改它了,让它能在多线程环境下正确的运行。

  如何修正在多线程环境下的bug

  这里我们会用到著名的双检锁技术,英文名就是“Double-Check Locking”,它是线程同步机制中的一种,它背后的思路是,如果对象已经构造好,就不需要线程同步,另外如果调用如上面提到的属性“Instance”的线程A发现对象没有创建好,就会获取一个线程同步锁来确保只有一个线程构造单例对象,基于这,我们将Demo类再稍微调整下

public class Demo1{private Demo1(){// to do
        }private static Demo1 _lock = new Demo1();private static Demo1 _instance;public static Demo1 Instance{get{if (_instance != null) return _instance;Monitor.Enter(_lock);if (_instance == null){Console.WriteLine(string.Format("线程{0}在{1}时刻发现Instance为null", Thread.CurrentThread.Name, DateTime.Now));_instance = new Demo1();}Monitor.Exit(_lock);return _instance;}}}

然后在调用类中再启动多两个线程CCC,DDD,再次启动程序

这次的结果表明只有一个线程创建了Demo类的实例了。其实上面的写法不是很严谨的,就是当私有构造器未执行完,其他的线程已经发现Instance不为null了,不过这个问题很难模拟出来。未了解决这种问题,那么就要用到Interlocked.Exchange() 这个方法。

  还有其他方式创建单例吗

  除了双检索技术,还有其他方式实现单例模式吗?答案是肯定的。先来看些下面这种方式

public class Demo2{private static Demo2 _demo2 = new Demo2();private Demo2(){Console.WriteLine(string.Format("线程{0}在{1}时刻执行私有构造函数", Thread.CurrentThread.Name, DateTime.Now));}public static Demo2 Instance{get { return _demo2; }}}

在看下执行结果图

那么它的原理是什么了?这里涉及到类型构造器了,由于当代码首次访问类的一个成员时,CLR 会自动调用一个类型的类构造器,所以当有一个线程访问属性Instance的时候,CLR会自动调用类构造器,从而创建这个对象的实例。

  总结

  这个话题已经被写乱了,如果我之前不仔细看项目里面的代码,我也不会发现这个问题,有些时候总是会被感觉所欺骗,所以最好的方法就是自己动手亲自实践一番,无非就是几个小时的事情而已。那么你看完这篇文章之后有没有什么感想了?

转载于:https://www.cnblogs.com/wucj/p/3157294.html

如何正确的使用单例模式相关推荐

  1. 最简单的单例模式,Go版本的实现你写对了吗?

    首先问大家一个问题,你们面试的时候,面试官有没有问过你们:"你都用过什么设计模式?",我猜多数人的回答会把单例模式,放在第一位. 我:"呃- 我用过单例.工厂.观察者,反 ...

  2. 《面试无忧》--DCL单例模式为什么要用volatile修饰?

    1.什么是DCL单例模式? DCL(double check locking)是一种双重检查的单例模式,在众多单例模式中,也算得上较优雅实用的一种,他使用两次synchronized代码块将实例化的过 ...

  3. C# 静态内部类单例模式-静态变量何时初始化

    对于一个类的静态变量何时初始化,大家都有一个普遍的共识,那就是第一次使用该类时,初始化该类的所有静态变量和静态方法. /// <summary>/// 只有在第一次使用到Test1的时候, ...

  4. 阿里,百度高级程序员力荐2019必看书单—附PDF电子档

    写在前面 程序员找出路还是要尽量提前进行职业规划和准备,千万不要说什么:"走一步,算一步"的话.在这个一睁眼就是竞争的时代,你可以放松休息,但别人会继续前进,不会等你. 1,Jav ...

  5. Unix编程/应用问答中文版(转)

    Unix编程/应用问答中文版 名称 -- Unix编程/应用问答中文版 版本 -- 0.04 ( 2003-10-09 外发版 ) 维护 -- 小四 <scz@nsfocus.com> 主 ...

  6. GNU Make 使用手册(中译版)

    如果要全面了解Linux的结构.理解Linux的编程总体设计及思想必须首先全部读通Linux源代码中各级的Makefile文件.目前,在网上虽然有一些著作,但都不能全面的解释Linux源代码中各级的M ...

  7. 单例模式的练习-如何正确构建

    2019独角兽企业重金招聘Python工程师标准>>> 1.说明:单例模式是最简单的设计模式之一,单例模式适合于一个类只有一个实例的情况. 2.要求:确保一个类只有一个实例被建立,并 ...

  8. Kotlin 静态内部类单例模式的正确实现方式

    本篇是对现网上流传的 Kotlin 实现静态内部类单例模式的纠正,为了把原理说清楚,文章前奏可能会有些长,熟悉静态内部类单例模式原理的朋友,可以直接跳转到文章最后,直接看结果即可. 最近在整理基础库的 ...

  9. java 单例 dcl_java 中单例模式DCL的缺陷及单例的正确写法

    1 前言 单例模式是我们经常使用的一种模式,一般来说很多资料都建议我们写成如下的模式: /** * Created by qiyei2015 on 2017/5/13. */ public class ...

最新文章

  1. linux 轻量化图形界面,用这13个方法,帮你做出真正轻量化的移动 App 设计
  2. 一、【绪论】数据结构的基本概念
  3. java实现接收字符串对象并在后台代码中转成list对象
  4. hbase数据导入到mysql(转载+自己验证整理,目前失败)
  5. 变量名和内存地址及符号表
  6. .写一个方法 void printScore(int score),输出相应的等级。score代表分数,等级格式如下:
  7. 用计算机表示45,计算机应用基础信息专业技术习题(45页)-原创力文档
  8. 一行python代码值多少钱_一行python代码
  9. 下一代微服务Service Mesh原理及实践
  10. [译] 逐渐去掌握 React(作为一名 Angular 开发者)
  11. MAC OS 修改环境变量
  12. 小米笔记本计算机在哪里,小米笔记本电脑配置在哪里查看
  13. egret 显示帧动画
  14. Uclinux、Linux区别
  15. 【不正经科普】一文读懂“区块链”
  16. spring boot参数校验 告别校验胶水代码
  17. VR虚拟展厅产品展示如何实现的
  18. 极光im java_java手写一个迷你版的Tomcat代码分享
  19. 自己努力要学习啦(android的很多demo 总有你需要的)
  20. 二进制文件、文本文件

热门文章

  1. 【AI不惑境】学习率和batchsize如何影响模型的性能?
  2. 全球及中国太阳能光热发电市场重点项目规划及发展格局展望报告2021-2027年
  3. 中国锂电池行业发展机遇及营销策略前景研究报告2021-2027年版
  4. 用动画实现android app启动界面的渐变效果
  5. Windows Server 2008 R2 下配置证书服务器和HTTPS方式访问网站
  6. 终于知道PUBWIN2009的数据库用户名与密码已经成功连接
  7. js实现焦点进入文本框内关闭输入法:imeMode
  8. 世界农业发展史-国际农民丰收节贸易会:人类的发展史
  9. 前端辅助开发工具积累
  10. Linux 配置JAVA_HOME