C#设计模式之Singleton模式

 更新时间:2020年06月11日 16:47:30   作者:老胡写代码  
这篇文章主要介绍了C#设计模式中的Singleton模式相关知识,文中代码非常详细,供大家理解学习,感兴趣的朋友可以了解下

前言

Singleton是二十三个设计模式中比较重要也比较经常使用的模式。但是这个模式虽然简单,实现起来也会有一些小坑,让我们一起来看看吧!

实现思路

首先我们看看这个设计模式的UML类图。

很清晰的可以看到,有三点是需要我们在实现这个模式的时候注意的地方。

  • 私有化的构造器
  • 全局唯一的静态实例
  • 能够返回全局唯一静态实例的静态方法

其中,私有化构造器是防止外部用户创建新的实例而静态方法用于返回全局唯一的静态实例供用户使用。原理清楚了,接下来我们看看一些典型的实现方式和其中的暗坑。

实现方法

最简单的实现方法

最简单的实现方法自然就是按照UML类图直接写一个类,我们看看代码。

 class Program
 {
  static void Main(string[] args)
  {
  	var single1 = Singleton.Instance;
   var single2 = Singleton.Instance;
   Console.WriteLine(object.ReferenceEquals(single1, single2));
   Console.ReadLine();
  }
 }

 class Singleton
 {
  private static Singleton _Instance = null;
  private Singleton()
  {
   Console.WriteLine("Created");
  }

  public static Singleton Instance
  {
   get
   {
    if (_Instance == null)
    {
     _Instance = new Singleton();
    }
    return _Instance;
   }
  }

  public void DumbMethod()
  {

  }
 }

这段代码忠实的实现了UML类图里面的一切,查看输出结果,

证实了Singleton确实起了作用,多次调用仅仅产生了一个实例,似乎这么写就可以实现这个模式了。但是,真的会那么简单吗?

如果多线程乱入?

现在我们给刚刚的例子加点调料,假设多个对实例的调用,并不是简单的,彬彬有礼的顺序关系,二是以多线程的方式调用,那么刚刚那种实现方法,还能从容应对吗?让我们试试。把Main函数里面的调用改成这样。

	static void Main(string[] args)
  {
   int TOTAL = 10000;
   Task[] tasks = new Task[TOTAL];
   for (int i = 0; i < TOTAL; i++)
   {
    tasks[i] = Task.Factory.StartNew(() =>
    {
     Singleton.Instance.DumbMethod();
    });
   }
			Task.WaitAll(tasks);
   Console.ReadLine();
  }

通过Factory创造出1万个Task,几乎同时去请求这个单例,看看输出。

咦,我们刚刚写的Singleton模式失效了,这个类被创造了5次(这段代码运行多次,这个数字不一定相同),一定是多线程搞的鬼,我们刚刚写的代码没有办法应对多线程,换句话说,是非线程安全的(thread-safe),那有没有办法来攻克这个难关呢?

线程安全的单例模式

Lock版本

提到线程安全,很多同学第一反应就是用lock,不错,lock是个可行的办法,让我们试试。添加一个引用类型的对象作为lock对象,修改代码如下(什么?你问我为什必须是引用类型的对象而不能是值类型的对象?因为lock的时候,如果对象是值类型,那么lock仅仅锁住了它的一个副本,另外一个线程可以畅通无阻的再次lock,这样lock就失去了阻塞线程的意义)

	private static object _SyncObj = new object();
  public static Singleton Instance
  {
   get
   {
    lock (_SyncObj)
    {
     if (_Instance == null)
     {
      _Instance = new Singleton();
     }
     return _Instance;
    }    
   }
  }

运行一下,输出

只有一个实例创建,证明Lock起作用了,这个模式可行!不过有些不喜欢用Lock的同学可能要问,还有没有其他办法呢?答案是有的。

静态构造器版本

回想一下,C#中的类静态构造器,只会在这个类第一次被使用的时候调用一次,天然的线程安全,那我们试试不用Lock使用类静态构造器?修改Singleton类如下:

 class Singleton
 {
  private static Singleton _Instance = null;
  private Singleton()
  {
   Console.WriteLine("Created");
  }

  static Singleton()
  {
   _Instance = new Singleton();
  }

  //private static object _SyncObj = new object();
  public static Singleton Instance
  {
   get { return _Instance; }
  }

  public void DumbMethod()
  {

  }
 }

去掉了Lock,添加了一个类静态构造器,试一试。

完美!对于不喜欢用Lock(在这个例子中,实例只会创建一次但是之后的所有线程都要先排队Lock再进入Critical code进行检查,效率比较低下)的同学,类静态构造器提供了一种很好的选择。
不过俗话说,人心苦不足 , 我们总是追求卓越。这个版本比Lock版本似乎更好一点,那还有没有更好的版本呢?有的。

Lazy版本

从net 4.0开始,C#开始支持延迟初始化,通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率。如果你不熟悉Lazy或者想更多了解它,请参考。我们今天关注的重点在于,Lazy也是天生线程安全的,所以我们尝试用它来实现Singleton模式?修改代码如下:

 class Singleton
 {
  private static Lazy<Singleton> _Instance = new Lazy<Singleton>(() => new Singleton());
  private Singleton()
  {
   Console.WriteLine("Created");
  }

  public static Singleton Instance
  {
   get
   {
    return _Instance.Value;
   }
  }

  public void DumbMethod()
  {

  }
 }

输出结果中可以看到,我们达到了想要的效果:

在上面的代码中,私有变量_Instance现在是被声明为延迟初始化,这样不但天然实现了线程安全,同时在没有调用Instance静态方法的时候(也即没有调用_Instance.Value),初始化不会发生,这样就提高了效率。

总结

Singleton模式很常见,实现起来也很简单,只是要小心线程安全。以上三种方法都可以实现线程安全的Singleton模式。如果net 版本在4.0之上,建议使用Lazy版本,毕竟对比Lock版本,Lazy版本可以免去实现手动Lock之苦,对比Static版本,又有延迟初始化的性能优势,何乐而不为呢?

以上就是C#设计模式之Singleton模式的详细内容,更多关于C#中的Singleton模式的资料请关注脚本之家其它相关文章!

相关文章

  • 详解C# 枚举高级用法之Description

    详解C# 枚举高级用法之Description

    这篇文章主要介绍了C# 枚举高级用法之Description,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 详解C#调用matlab生成的dll库

    详解C#调用matlab生成的dll库

    这篇文章主要介绍了C#调用matlab生成的dll库,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • C#无法打开计算机“.”上的 IISADMIN 服务的解决方法

    C#无法打开计算机“.”上的 IISADMIN 服务的解决方法

    在使用c#进行控制IIS服务启动停止的时候,提示:无法打开计算机“.”上的 IISADMIN 服务
    2015-01-01
  • C# WinForm实现Win7 Aero透明效果代码

    C# WinForm实现Win7 Aero透明效果代码

    这篇文章主要介绍了C# WinForm实现Win7 Aero透明效果代码,通过调用dwmapi.dll相关方法实现,需要的朋友可以参考下
    2014-07-07
  • C#登入实例

    C#登入实例

    本篇文章通过截图的方式向大家展示C#程序登陆实现的全过程,利用了C#三层架构的编写方法,希望对大家今后编写代码有所帮助
    2016-11-11
  • C# 如何实现一个带通知的List<T>

    C# 如何实现一个带通知的List<T>

    这篇文章主要介绍了C# 如何实现一个带通知的List<T>,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-02-02
  • WPF自定义控件实现ItemsControl鱼眼效果

    WPF自定义控件实现ItemsControl鱼眼效果

    这篇文章主要为大家详细介绍了WPF如何通过自定义控件实现ItemsControl鱼眼效果,文中的示例代码讲解详细,需要的可以参考一下
    2024-01-01
  • C#基础之委托用法实例教程

    C#基础之委托用法实例教程

    这篇文章主要介绍了C#中委托的用法,包括委托的功能、定义及用法示例,需要的朋友可以参考下
    2014-09-09
  • C#向PPT文档插入图片以及导出图片的实例

    C#向PPT文档插入图片以及导出图片的实例

    PowerPoint演示文稿是我们日常工作中常用的办公软件之一,本篇文章介绍了C#向PPT文档插入图片以及导出图片的实例,非常具有实用价值,需要的朋友可以参考下。
    2016-12-12
  • C#中TransactionScope的使用小结

    C#中TransactionScope的使用小结

    本篇文章主要是对C#中TransactionScope的使用方法进行了详细的介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01

最新评论