软件编程
位置:首页>> 软件编程>> C#编程>> C#实现单例模式的几种方法总结

C#实现单例模式的几种方法总结

作者:zls365  发布时间:2023-10-13 16:33:55 

标签:c#,单例模式
目录
  • 介绍

  • Version 1 - 非线程安全

  • Version 2 - 简单的线程安全

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

  • Version 5 - 完全懒汉实例化

    • Version 6 - 使用.NET 4 Lazy<T> type 特性

      • 总结

        介绍

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

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

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

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

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

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

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

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

        Version 1 - 非线程安全


        /// <summary>
        /// Bad code!Do not use!
        /// </summary>
        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的线程安全


        /// <summary>
        /// Bad code ! Do not use!
        /// </summary>
        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
        {
         //Explicit static constructor to tell C# compiler
         //not to mark type as beforefieldinit
         static Nested()
         {
         }
         internal static readonly Singleton5 instance = new Singleton5();
        }
        }

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

        Version 6 - 使用.NET 4 Lazy<T> 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<T> type来实现完全懒汉式。其代码看起来也很简洁且性能表现也很好。

        性能 VS 懒汉式

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

        本文翻译自Implementing the Singleton Pattern in C#, 作者在文中做了一些循环测试,具体的读者可直接阅读原文。

        Exception

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

        总结

        上述提供的几种实现方法中,一般情况下提倡使用Version 4,除非遇到有时早于单列类实例化时就引用了其他静态成员。这种情况下,Version 2一旦被考虑,虽然它看起来会因加锁耗时,但是其实运行起来并没有你想的那么慢,关键是你很容易写对它。

        显然Version 1你永远都不应该考虑,Version 3在与Version 5的对比下也是不在考虑范围之内的。

        来源:https://mp.weixin.qq.com/s/9VDrsYv15PD5zZOd55NhJA

        0
        投稿

        猜你喜欢

        • 集合、数组都是对多个数据进行存储操作(主要是内存层面存储)的结构,简称Java容器。数组的特点1.数组初始化以后,长度确定不可变2.数组定义
        • 适用场合: 7.3工厂模式的适用场合 创建新对象最简单的办法是使用new关键字和具体类。只有在某些场合下,创建和维护对象工厂所带来的额外复杂
        • 1.问题产生情况我遇到这个问题是做微信开发的时候有些有用的头像用了微信的emoji表情,然而我的mysql数据库用的编码是utf8_gene
        • DataGridView:显示数据表后台数据绑定:List<xxx> list = new List<xxx>();
        • 前言早期在学习泛型的协变与逆变时,网上的文章讲解、例子算是能看懂,但关于逆变的具体应用场景这方面的知识,我并没有深刻的认识。本文将在具体的场
        • 使用Myeclipse搭建maven项目准备工作安装maven官网下载安装(http://maven.apache.org/)配置环境变量配
        • 本文实例为大家分享了Java实现员工管理系统的具体代码,供大家参考,具体内容如下本系统主要练习到的相关内容: 1、 流程控制语句 2、 类、
        • 对于一个应用程序而言,控件是搭建用户界面的积木。它们具备交互式的特征。VS 2012中的控件都放在工具箱中,添加一个控件到窗体,只需在工具箱
        • 本文实例讲述了Java实现分解任意输入数的质因数算法。分享给大家供大家参考,具体如下:分解任意输入数的质因数:质因数概念:任何一个合数都可以
        • 表达式树的概念表达式树的创建有 Lambda法 和 组装法。学习表达式树需要 委托、Lambda、F
        • 概述从今天开始, 小白我将带大家开启 Jave 数据结构 & 算法的新篇章.算法的衡量标准当我们需要衡量一个算法的的优越性, 通常会
        • 当遇到以下场景:其他人写的单元测试影响统计结果一些需要调用外部接口的测试暂不运行需要在非本机环境上运行一些不回滚的单元测试则有必要选择以下方
        • 介绍这里学习SpringSecurity,对SpringSecurity进行学习。基本用法添加依赖<dependency> &n
        • 在使用手机时,当有未接来电或者新短消息时,手机会给出响应的提示信息,这些提示信息通常会显示到手机屏幕的状态栏上。Android也提供了用于处
        • 为了打造流畅的用户导航体验,我们不得不依赖智能手机最常见的一个功能:触摸。触摸改变应用程序的视图是现在最流行一种导航设计。在本文中,我们将经
        • 问题提出:自己在做一个小网站充当练手,但是前端图片经过base64加密后传往后端在解码。但是一直都有问题,请大神赐教  publi
        • 本文实例讲述了Android基于SoftReference缓存图片的方法。分享给大家供大家参考,具体如下:Java中的SoftReferen
        • 前言Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expe
        • 目录卡顿原理卡顿监控ANR原理卡顿原理主线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR。 应用进程启动时候,Zygote会反射调用Act
        • 一、通过Java代码在setContentView之前执行:requestWindowFeature(Window.FEATURE_NO_T
        手机版 软件编程 asp之家 www.aspxhome.com