C#设计模式之单例模式

news2024/11/24 18:40:13

介绍

单例模式(Singleton)保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式的结构图如下所示:

image-20240108164847808

使用单例模式的原因

对一些类来说,只有一个实例是很重要的。如何才能保证一个类只有一个实例并且这个实例易于被访问呢?

基于程序员之间的约定或是利用全局变量吗?

虽然这样或许也可以实现,但是单例模式确是更好的做法。因为仅仅靠约定或是全局变量,如果重新new一个对象,还是可以创建新的实例。

经典的单例模式

现在我们先来看看经典的单例模式写法:

  public class Singleton
  {
      private static Singleton? _instance;
      private Singleton() { }
      public static Singleton GetInstance()
      {
          if(_instance == null)
            _instance = new Singleton();
         return _instance;
      }
  }
private static Singleton? _instance;

声明了一个静态的类变量,静态类变量在类的所有实例之间共享, 与类关联而不是与类的实例关联,它在整个应用程序域中只有一个实例。

 private Singleton() { }

私有的构造函数,这使得我们无法通过:

 Singleton Singleton = new Singleton();

这种写法来创建它的实例,这样写会报错,如下所示:

image-20240108152540820

  public static Singleton GetInstance()
      {
          if(_instance == null)
            _instance = new Singleton();
         return _instance;
      }

该方法是获得本类实例的唯一全局访问点。

如果实例不存在,在类的内部可以通过new操作符来获取一个实例,否则返回已有的实例。

 internal class Program
 {
     static void Main(string[] args)
     {        
         Singleton singleton1 = Singleton.GetInstance();
         Singleton singleton2 = Singleton.GetInstance();
         if (singleton1 == singleton2)
             Console.WriteLine("两个对象是相同的实例");
     }
 }

比较两个对象看看它们是不是同一个实例,运行结果如下所示:

image-20240108153030471

多线程下的单例模式

上述代码在多线程情况下会存在问题,如果有多个线程同时访问Singleton类调用GetInstance方法,会有可能造成创建多个实例。

查看以下代码:

internal class Program
{
    static void Main(string[] args)
    {        
        // 创建并启动两个任务
        Task task1 = Task.Factory.StartNew(Method1);
        Task task2 = Task.Factory.StartNew(Method2);
        Task.WaitAll(task1, task2);        
        Console.WriteLine("所有任务完成");
        void Method1()
        {
            Console.WriteLine("Task 1 is running.");
            Task.Delay(1000).Wait();
            Singleton singleton1 = Singleton.GetInstance();
            Console.WriteLine(singleton1.GetHashCode());
        }

         void Method2()
        {         
            Console.WriteLine("Task 2 is running.");
            Task.Delay(1000).Wait();
            Singleton singleton2 = Singleton.GetInstance();
            Console.WriteLine(singleton2.GetHashCode());
        }
    }
}

运行结果如下所示:

image-20240108155043249

两个对象的哈希码不同,在C#中,每个对象都有一个GetHashCode方法,该方法返回该对象的哈希码。默认情况下,GetHashCode方法是根据对象的内存地址生成的,因此两个不同的实例在内存中有不同的地址,它们的哈希码通常也是不同的。然而,这并不是说哈希码不同就一定表示两个对象是不同的。因为哈希码是一个有限的整数,存在哈希冲突的可能性。两个不同的对象可能具有相同的哈希码,这被称为哈希冲突。因此,不能仅仅通过比较哈希码就断定两个对象是相同的还是不同的。

但是我们在这里我们可以这样进行简单的判断。

那么该如何解决这个问题呢?

使用lock

C#中可以使用lock来解决。 lock 语句可确保在任何时候最多只有一个线程执行其主体。

可以这样进行改写:

 public class Singleton
 {
     private static Singleton? _instance;
     private static readonly object _syncRoot = new object();
     private Singleton() { }
     public static Singleton GetInstance()
     {
         lock (_syncRoot)
         {
             if (_instance == null)
                 _instance = new Singleton();
         }
        return _instance;
     }
 }
 private static readonly object _syncRoot = new object();

程序运行时创建一个静态只读的辅助对象。

  public static Singleton GetInstance()
     {
         lock (_syncRoot)
         {
             if (_instance == null)
                 _instance = new Singleton();
         }
        return _instance;
     }

lock语句确保在任何时候最多只有一个线程执行其主体。

现在再来看看运行结果:

image-20240108160735396

双重锁定

现在两个对象的哈希码一样了,在这里我们可以简单的认为是同一个实例了。

但是每次都要执行lock语句会影响性能,还需要改进:

  public class Singleton
  {
      private static Singleton? _instance;
      private static readonly object _syncRoot = new object();
      private Singleton() { }
      public static Singleton GetInstance()
      {
          if (_instance == null)
          {
              lock (_syncRoot) 
              { 
                  if(_instance == null) 
                  {
                      _instance = new Singleton();
                  }
              }
          }               
         return _instance;
      }
  }

这样子当存在_instance时,直接返回,没有执行lock语句。为什么执行两次if(_instance == null)判断是因为当_instance == null时,如果同时有两个线程调用GetInstance方法,它们都可以通过第一次判断,由于lock的机制,这两个线程只有一个进去,另一个在外排队等候,必须等上一个进入并且退出之后,第二个线程才能进入,而此时如果没有第二层的判断,那么第一个线程与第二个线程都会创建新的实例,而添加了这个判断之后,第一个线程创建了实例,_instanc就不为空,第二个线程就无法创建新的实例了。

现在再来看看运行结果:

image-20240108165337005

静态初始化

其实在实际应用中,C#也可以采用静态初始化的方法,这种方法不需要开发人员显式地编写线程安全代码,即可解决多线程环境下不安全的问题。

 public sealed class Singleton
 {
     private static readonly Singleton _instance = new Singleton();     
     private Singleton() { }
     public static Singleton GetInstance()
     {        
       return _instance;
     }
 }

使用sealed关键字表示是密封类,阻止发生派生,因为派生可能会增加实例。

 private static readonly Singleton _instance = new Singleton();     

在第一次引用类的任何成员时创建实例,公共语言运行时负责处理变量初始化。

现在再来看看运行结果:

image-20240108165241124

由于这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉单例类,原先的单例模式的处理方式是要在第一次被引用时,才会将自己实例化,所以就被称为懒汉单例类

总结

本文介绍了在C#中如何使用单例模式,并介绍了在多线程模式下单例模式可能存在的问题及其解决方法,希望对你有所帮助。

参考

1、《Head First 设计模式(中文版)》

2、《大话设计模式》

3、《设计模式:可复用面向对象软件的基础》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1366749.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JAVA】Iterator 怎么使用?有什么特点

🍎个人博客:个人主页 🏆个人专栏: JAVA ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 Iterator 接口的主要方法: 例子 特点: 结语 我的其他博客 前言 在编程的世界里,迭代…

【QML COOK】- 000-创建Project

1. 文件->New Project... 2. Application(Qt)->Qt Quick Application(compat) 3. 填好【名称】和【创建路径】 4. 选择CMake 5. 选择QT6.2 6. 直接【下一步】 7. 直接下一步 8. 直接下一步 9. 出现工程文件 10. 点击运行 11. 出现窗口

npm报错error:03000086:digital envelope routines::initialization error

可能是因为node版本过高,与现在的项目不符合 这是降低node版本的命令,然后重新运行 npm install npm8.1.2 -g

张留念瞰游天下总经理与汤加政府签署合作备忘 将于2024年隆重启动

汤加是南太平洋上一个美丽的岛国,是南太平洋唯一维持君主立宪制的国家,全国由173个海岛组成,海洋、海岛旅游资源丰富,是个原生态的旅游胜地。 中汤两国建交25年以来,两国政府高级官员和代表团互访频繁,在政…

【源码预备】Calcite基础知识与概念:关系代数概念、查询优化、sql关键字执行顺序以及calcite基础概念

文章目录 一. 关系代数的基本知识二. 查询优化三. SQL语句的解析顺序1. FROM2. WHERE3. GROUP BY4. HAVING5. SELECT 四. Apache Calcite中的基本概念1. Adapter2. Calcite中的关系表达式2.1. 关系表达式例子2.2. 源码底层结构 3. Calcite的优化规则4. Calcite的Trait--算子物理…

自动挡的闪300?QJ要做第一个吃螃蟹的人了?

其实在发布会当天,有一台车在现场热度是比赛921还高的,因为四缸大跑车大家可能都见多了,而QJ带来一台自动挡的闪300,这个自动挡其实是需要加个引号的,因为升档的时候是需要你按一下手把上的按键的,有点类似…

设计模式的艺术P1基础—2.4-2.11 面向对象设计原则

设计模式的艺术P1基础—2.4-2.11 面向对象设计原则 2.4 面向对象设计原则概述 向对象设计的目标之一在于支持可维护性复用,一方面需要实现设计方案或者源代码的重用,另一方面要确保系统能够易于扩展和修改,具有较好的灵活性。 面向对象设计…

基于Java SSM框架实现实现机房预约系统项目【项目源码+论文说明】

基于java的SSM框架实现机房预约系统演示 摘要 21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识&#…

这款Web剪藏工具绝了,支持10+平台内容剪辑同步!

前言 Web Clipper 是一个开源项目,旨在帮助用户轻松地保存和组织网页内容。它可以作为浏览器插件安装到常见的浏览器中,如Chrome、Firefox 等,用户可以使用它来保存网页、截取文章、添加标签和注释等操作,从而方便地管理和分享自…

[VUE]4-状态管理vuex

目录 状态管理 vuex 1、vuex 介绍 2、安装 3、使用方式 4、总结 🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法,初步涉猎Python人工智…

20240108移远的4G模块EC20在Firefly的AIO-3399J开发板的Android11下调通的步骤

20240108移远的4G模块EC20在Firefly的AIO-3399J开发板的Android11下调通的步骤 2024/1/8 17:50 缘起:使用友善之臂的Android11可以让EC20上网,但是同样的修改步骤,Toybrick的Android11不能让EC20上网。最后确认是selinux的问题! …

亲测有效:腾讯云免费服务器30天申请流程

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM,轻量配置可选2核2G3M、2核8G7M和4核8G12M,CVM云服务器可选2核2G3M和2核4G3M配置,腾讯云百科txybk.com分享2024年最新腾讯云免费服务器…

nodejs 不用 electron 实现打开文件资源管理器并选择文件

前言 最近在开发一些小脚本,用 nodejs 实现。其中很多功能需要选择一个/多个文件,或者是选择一个文件夹。 最初的实现是手动输入一个目录(这个只是一个普通的终端文本输入,所以按下 tab 没有路径提示),非…

用可视化案例讲Rust编程1. 怎么能学会Rust

用可视化案例讲Rust编程 1. 怎么能学会Rust 如果要列举Rust的优势,恐怕写个十条八条是写不完的,而且不管写哪条优势,都有很多同学跳起来反驳,比如我们说Rust比C/C内存安全,肯定有同学说C 20也支持内存安全&#xff0…

QT布局组件

时间记录:2024/1/8 一、整个页面各位置的详细图片 这些的布局都可以通过QSS来进行修改 二、非QSS的一些布局组件 2.1 QHBoxLayout水平布局组件 常用属性: (1)spacing:子组件间的间距 (2)st…

关于电脑屏幕亮度的调整,看这篇文章就够了

你可能需要定期更改屏幕亮度。当外面很亮的时候,你想把它调大,这样你就能看到。当你在黑暗的房间里时,你会希望它变暗,这样就不会伤害你的眼睛。降低屏幕亮度也有助于节省电力并延长笔记本电脑的电池寿命。 除了手动更改屏幕亮度外,Windows还可以通过多种方式自动更改屏幕…

MySQL 8.0 InnoDB 架构之 日志缓冲区(Log Buffer)和重做日志(Redo Log)

文章目录 MySQL 8.0 InnoDB 架构之 日志缓冲区(Log Buffer)和重做日志(Redo Log)REDO相关主要参数innodb_log_buffer_size innodb_redo_log_capacityinnodb_log_group_home_dir参考 【免责声明】文章仅供学习交流,观点…

进程间通信之匿名管道和命名管道的理解和实现【Linux】

进程间通信之匿名管道和命名管道的理解和实现 进程间通信什么是管道匿名管道代码实现管道的读写规则管道特点 命名管道创建命名管道代码实现 进程间通信 进程间通信的目的 数据传输:一个进程需要将它的数据发送给另一个进程资源共享:多个进程之间共享同…

java基于ssm框架的滁艺咖啡在线销售系统+vue论文

摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装滁艺咖啡在线销售系统软件来发挥其高效地信息处理的作用&am…

对接讯飞聊天机器人接口--复盘

1、准备工作 1)、进入以下平台进行注册,登录后,点击红框处 2)、点击个人免费包(会弹出实名认证,先进行实名认证) 3)、认证后,会进入以下界面,先添加应用 4&am…