C#实现单例模式的6种方法小结

作者:五维思考 时间:2023-09-13 21:02:57 

介绍

单例模式是软件工程学中最富盛名的设计模式之一。从本质上看,单例模式只允许被其自身实例化一次,且向外部提供了一个访问该实例的接口。通常来说,单例对象进行实例化时一般不带参数,因为如果不同的实例化请求传递的参数不同的话会导致问题的产生。(若多个请求都是传递的同样的参数的话,工厂模式更应该被考虑)

C#中实现单例有很多种方法,本文将按顺序介绍非线程安全、完全懒汉式、线程安全和低/高性能集中版本。

在所有的实现版本中,都有以下几个共同点:

  • 唯一的、私有的且无参的构造函数,这样不允许外部类进行实例化;

  • 类是密封的,尽管这不是强制的,但是严格来讲从上一点来看密封类能有助于JIT的优化;

  • 一个静态变量应该指向类的唯一实例;

  • 一个公共的静态变量用于获得这个类的唯一实例(如果需要,应该创建它);

需要注意的是,本文中所有的例子中都是用一个 public static Instance的变量来访问单例类实例,要将其转换成公共函数是很容易的,但是这样并不会带来效率和线程安全上的提升。

Version 1 - 非线程安全

public sealed class Singleton
{
   private static Singleton instance = null;
   private Singleton() { }
   public static Singleton Instance
   {
       get
       {
           if (instance == null)
           {
               instance = new Singleton();
           }
           return instance;
       }
   }
}

该版本在多线程下是不安全的,会创建多个实例,请不要在生产环境中使用!

因为如果两个线程同时运行到if(instance==null)判断时,就会创建两个实例,这是违背单例模式的初衷的。实际上在后面那个线程进行判断是已经生成了一个实例,但是对于不同的线程来说除非进行了线程间的通信,否则它是不知道的。

Version 2 - 简单的线程安全

public sealed class Singleton2
{
   private static Singleton2 instance = null;
   private static readonly object obj = new object();
   private Singleton2() { }
   public Singleton2 Instance
   {
       get
       {
           lock (obj)
           {
               if (instance == null)
               {
                   instance = new Singleton2();
               }
               return instance;
           }
       }
   }
}

该版本是线程安全的。通过对一个过线程共享的对象进行加锁操作,保证了在同一时刻只有一个线程在执行lock{}里的代码。当第一个线程在进行instance判断或创建时,后续线程必须等待直到前一线程执行完毕,因此保证了只有第一个线程能够创建instance实例。

但不幸的是,因为每次对instance的请求都会进行lock操作,其性能是不佳的。

需要注意的是,这里使用了一个private static object变量进行锁定,这是因为当如果对一个外部类可以访问的对象进行锁定时会导致性能低下甚至死锁。因此通常来说为了保证线程安全,进行加锁的对象应该是private的。

Version 3 - Double-check locking的线程安全

public sealed class Singleton3
{
   private static Singleton3 instance = null;
   private static object obj = new object();
   private Singleton3() { }
   public static Singleton3 Instance
   {
       get
       {
           if (instance == null)
           {
               lock (obj)
               {
                   if (instance == null)
                   {
                       instance = new Singleton3();
                   }
               }
           }
           return instance;
       }
   }
}

该版本中试图去避免每次访问都进行加锁操作并实现线程安全。然后,这段代码对Java不起作用,因Java的内存模型不能保证在构造函数一定在其他对象引用instance之前完成。还有重要的一点,它不如后面的实现方式。

Version 4 - 不完全懒汉式,但不加锁的线程安全

public sealed class Singleton4
{
   private static readonly Singleton4 instance = new Singleton4();
   /// <summary>
   /// 显式的静态构造函数用来告诉C#编译器在其内容实例化之前不要标记其类型
   /// </summary>
   static Singleton4() { }
   private Singleton4() { }
   public static Singleton4 Instance { get { return instance; } }
}

这个版本是的实现非常的简单,但是却又是线程安全的。C#的静态构造函数只有在当其类的实例被创建或者有静态成员被引用时执行,在整个应用程序域中只会被执行一次。使用当前方式明显比前面版本中进行额外的判断要快。

当然这个版本也存在一些瑕疵:

  • 不是真正意义上的懒汉模式(需要的时候才创建实例),若单例类还存在其他静态成员,当其他类第一次引用这些成员时便会创建该instance。下个版本实现会修正这个问题;

  • 只有.NET中才具有beforefieldinit特性,即懒汉式实现。且在.Net 1.1以前的编译器不支持,不过这个现在来看问题不大;

所有版本中,只有这里将instance设置成了readonly,这不仅保证了代码的高校且显得十分短小。

Version 5 - 完全懒汉实例化

public sealed class Singleton5
{
   private Singleton5() { }
   public static Singleton5 Instance { get { return Nested.instance; } }
   private class Nested
   {
       static Nested() { }
       internal static readonly Singleton5 instance = new Singleton5();
   }
}

该版本看起来稍微复杂难懂,其实只是在写法上实现了上一版本的瑕疵,通过内嵌类的方式先实现了只有在真正应用Instance时才进行实例化。其性能表现与上一版本无异。

Version 6 - 使用.NET 4 Lazy type 特性

public sealed class Singleton6
{
   private static readonly Lazy<Singleton6> lazy =
          new Lazy<Singleton6>(()=> new Singleton6());
   public static Singleton6 Instance { get { return lazy.Value; } }
   private Singleton6() { }
}

如果你使用的是.NET 4或其以上版本,可以使用System.Lazy type来实现完全懒汉式。其代码看起来也很简洁且性能表现也很好。

性能 VS 懒汉式

一般情况下,我们并不需要实现完全懒汉式,除非你的构造初始化执行了某些费时的工作。因此一般的,我们使用显式的静态构造函数就能够适用。

本文翻译自Implementing the Singleton Pattern in C#

Exception

有时候在进行构造函数初始化时可能 会抛出异常,但这对整个应用程序来说不应该是致命的,所以可能的情况下,你应该自己处理这种异常情况。

来源:https://www.cnblogs.com/zhaoshujie/p/14323654.html

标签:C#,单例模式
0
投稿

猜你喜欢

  • Android setTag方法的key问题解决办法

    2021-09-12 14:29:37
  • Android搜索结果显示高亮实例(有数据滑动底部自动刷新)

    2021-09-25 22:55:07
  • 详解Spring Boot Profiles 配置和使用

    2021-10-05 22:54:57
  • Java高级特性之反射机制实例详解

    2023-10-08 06:33:51
  • Flutter中嵌入Android 原生TextView实例教程

    2023-07-05 02:02:00
  • Java分布式服务框架Dubbo介绍

    2022-09-16 01:27:53
  • 3种C# 加载Word的方法

    2021-06-05 21:06:41
  • Java基础之创建虚拟机对象的过程详细总结

    2022-07-07 14:24:29
  • 详解Android使用CoordinatorLayout+AppBarLayout实现拉伸顶部图片功能

    2023-04-27 16:55:29
  • C# 常用日期时间函数(老用不熟)

    2021-08-21 10:12:18
  • C#中Dynamic和Dictionary性能比较

    2022-05-11 12:16:33
  • java反射使用示例分享

    2023-07-02 20:18:59
  • Java动态代 理分析及简单实例

    2023-11-24 21:14:56
  • Spring Cloud Gateway集成Sentinel流控详情

    2023-11-09 20:27:31
  • springboot注册拦截器所遇到的问题

    2023-01-17 21:18:30
  • Springmvc ajax跨域请求处理方法实例详解

    2023-08-25 22:56:46
  • Java Springboot 重要知识点整理汇总

    2022-03-17 01:06:57
  • Javaweb开发环境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程

    2023-11-15 21:47:05
  • Spring Data JPA 设置字段默认值方式

    2021-08-13 07:27:06
  • Unity 按钮事件封装操作(EventTriggerListener)

    2022-07-08 10:07:08
  • asp之家 软件编程 m.aspxhome.com