当前位置: 首页 > news >正文

C#设计模式之单例模式

介绍

单例模式(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、《设计模式:可复用面向对象软件的基础》

相关文章:

C#设计模式之单例模式

介绍 单例模式(Singleton)保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式的结构图如下所示: 使用单例模式的原因 对一些类来说,只有一个实例是很重要的。如何才能保证一个类只有一个实例并且这个…...

【源码预备】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--算子物理…...

【Java 设计模式】23 种设计模式

文章目录 设计模式是什么计算机行业里的设计模式创建型模式(共 5 种)结构型模式(共 7 种)行为型模式(共 11 种) 总结 设计模式是什么 “每一个模式描述了一个在我们周围不断重复发生的问题,以及…...

ElasticSearch深度分页解决方案

一、前言 ElasticSearch是一个基于Lucene的搜索引擎,它支持复杂的全文搜索和实时数据分析。在实际应用中,我们经常需要对大量数据进行分页查询,但是传统的分页方式在处理大量数据时会遇到性能瓶颈。本文将介绍ElasticSearch分页工作原理、深…...

nginx下upstream模块详解

目录 一:介绍 二:特性介绍 一:介绍 Nginx的upstream模块用于定义后端服务器组,以及与这些服务器进行通信的方式。它是Nginx负载均衡功能的核心部分,允许将请求转发到多个后端服务器,并平衡负载。 在upst…...

基于ssm的双减后初小教育课外学习生活活动平台的设计与实现论文

双减后初小教育课外学习生活活动平台的设计与实现 摘 要 当下,正处于信息化的时代,许多行业顺应时代的变化,结合使用计算机技术向数字化、信息化建设迈进。以前学校对于课外学习活动信息的管理和控制,采用人工登记的方式保存相关…...

wblogic中间件配置数据源

配置数据源 1.服务-数据源-配置-新建 2.单机选一般数据源 3.选择源名称、jndi名称、数据库类型 4.选择驱动 5.下一步 6.输入连接串信息 参考&#xff1a; 格式二&#xff1a;jdbc:oracle:thin:<host>:<port>:<SID> 数据库名称配置的sid 7.测试配置&#xff…...

Java数据结构之装箱拆箱

装箱和拆箱 也叫装包拆包&#xff0c;装包是把那八种基本数据类型转换为它的包装类&#xff0c;拆包则相反 上面这俩种方式都是装包&#xff0c;下面是它的字节码文件 用到了Integer的ValueOf方法&#xff1a; 就是返回了一个Integer类的对象&#xff0c;把它的value属性设置成…...

各版本 操作系统 对 .NET Framework 与 .NET Core 支持

有两种类型的受支持版本&#xff1a;长期支持 (LTS) 版本和标准期限支持 (STS) 版本。 所有版本的质量都是一样的。 唯一的区别是支持的时间长短。 LTS 版本可获得为期三年的免费支持和补丁。 STS 版本可获得 18 个月的免费支持和修补程序。 有关详细信息&#xff0c;请参阅 .N…...

Golang 线程安全与 sync.Map

前言 线程安全通常是指在并发环境下&#xff0c;共享资源的访问被适当地管理&#xff0c;以防止竞争条件&#xff08;race conditions&#xff09;导致的数据不一致 Go语言中的线程安全可以通过多种方式实现 实现方式 互斥锁&#xff08;Mutexes&#xff09; Go的sync包提供…...

1.2 Hadoop概述

小肥柴的Hadoop之旅 1.2 Hadoop概述 目录1.2 Hadoop概述1.2.1 回归问题1.2.2 Google的三篇论文1.2.3 Hadoop的诞生过程1.2.4 Hadoop特点简介 参考文献和资料 ) 目录 1.2 Hadoop概述 1.2.1 回归问题 通过前一篇帖子的介绍&#xff0c;特别是问题思考部分的说明&#xff0c;我…...

Adams许可管理安全控制策略

随着全球信息化的快速发展&#xff0c;信息安全和许可管理问题日益凸显。在这场无形的挑战中&#xff0c;Adams许可管理安全控制策略以其卓越的性能和可靠性&#xff0c;引领着解决这类问题的新潮流。 Adams许可管理安全控制策略是一种全方位、多层次的安全控制方案&#xff0…...

无人地磅系统|内蒙古中兴首创无人地磅和远程高效管理的突破

走进标杆企业&#xff0c;感受名企力量&#xff0c;探寻学习优秀企业领先之道。 本期要跟砼行们推介的标杆企业是内蒙古赤峰市砼行业的龙头企业&#xff1a;赤峰中兴首创混凝土搅拌有限责任公司&#xff08;以下简称为中兴首创&#xff09;。 中兴首创成立于2011年初&#xff…...

【SpringCloud】7、Spring Cloud Gateway限流配置

1、限流介绍 Spring Cloud Gateway 的限流配置主要涉及到令牌桶算法的实现。令牌桶算法可以对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。 在 Spring Cloud Gateway 中,官方提供了 RequestRateLimiterGatewayFi…...

【gRPC学习】使用go学习gRPC

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 RPC是远程调用,而google实现了grpc比较方便地实现了远程调用,gRPC是一个现代的开源远程过程调用(RPC)框架 概念介绍 在gRPC中&#xff0c;客户端应用程序可以直接调用另一台计算机上的服务器应用程序上的方法&#…...

C语言中常用的字符串函数(strlen、sizeof、sscanf、sprintf、strcpy)

C语言中常用的字符串函数 文章目录 C语言中常用的字符串函数1 strlen函数2 sizeof函数2.1 sizeof介绍2.2 sizeof用法 3 sscanf函数3.1 sscanf介绍3.2 sscanf用法3.3 sscanf高级用法 4 sprintf函数4.1 背景4.2 sprintf用法 5 strcpy函数5.1 strcpy介绍5.1 strcpy用法 1 strlen函…...

域名解析服务器:连接你与互联网的桥梁

域名解析服务器&#xff1a;连接你与互联网的桥梁 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;我们将探讨一个网络世界中至关重要却鲜为人知的角…...

理论物理在天线设计和射频电路设计中的应用

理论物理的基本原理可以应用于电路中的电磁场分析和电磁波传播问题&#xff0c;例如天线设计和射频电路设计。通过应用麦克斯韦方程组和电磁波传播理论&#xff0c;可以优化电路的性能&#xff0c;提高天线的辐射效率和射频电路的传输效率。麦克斯韦方程组是描述电磁场行为的基…...

MySql01:初识

1.mysql数据库2.配置环境变量3. 列的类型和属性&#xff0c;索引&#xff0c;注释3.1 类型3.2 属性3.3 主键(主键索引)3.4 注释 4.结构化查询语句分类&#xff1a;5.列类型--表列类型设置 1.mysql数据库 数据库&#xff1a; ​ 数据仓库&#xff0c;存储数据&#xff0c;以前我…...

Python——运算符

num 1 num 1 print("num1:", num) num - 1 print("num-1:", num) num * 4 print("num*4:", num) num / 4 print("num/4:", num) num 3 num % 2 print("num%2:", num) num ** 2 print("num**2:", num) 运行结果…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...