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

学习设计模式《四》——单例模式

一、基础概念

        单例模式的本质【控制实例数目】;

        单例模式的定义:是用来保证这个类在运行期间只会被创建一个类实例;单例模式还提供了一个全局唯一访问这个类实例的访问点(即GetInstance方法)单例模式只关心类实例的创建问题,并不关心具体的业务功能

        单例模式的范围:在C#中单例模式的范围是指在每个AppDomain之中只能存在一个实例的类;  在Java中单例的范围是一个虚拟机的范围(因为装载类的功能是虚拟机,一个虚拟机通过自己的ClassLoader装载单例);

        单例模式的命名:建议使用GetInstance()作为单例模式的方法名;GetInstance()方法可以有参数。

何时选用单例模式?

1、当需要控制类的实例只能有一个,且客户只能从一个全局访问点访问它;(常用到单例的场景有:配置内容、数据库等连接资源、文件资源等)。

单例模式的优点
序号单例模式的优点
1  时间与空间
(懒汉式是典型的时间换空间【每次获取实例都会进行判断是否需要创建实例,浪费判断的时间,若一直没有人使用就不会创建实例,节约内存】;
 饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用先创建出来,每次调用时就不需要再判断了,节省运行时间
2线程安全
饿汉式是线程安全的,因为在装载的时候只会装载一次,且在装载类的时候不会发生并发;
从线程安全性上来讲,不加同步的懒汉式线程是不安全的【即;有多个线程同时调用GetInstance方法就可能导致并发问题】)

二、单例模式示例

        我们在项目开发过程中,经常会涉及到配置文件的内容;比如我们现在要实现读取配置文件内容,应该如何实现?

 2.1、未使用任何模式

1、编写不使用任何模式直接读取配置文件类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SingletonPattern
{/// <summary>/// 配置文件类(不使用模式)/// </summary>internal class AppConfig{private string appConfigPathAndName = $"{Directory.GetCurrentDirectory()}\\AppConfig.txt";//存放配置文件中参数A的值private string parameterA;//存放配置文件中参数B的值private string parameterB;public AppConfig(){CreateConfig();ReadConfig();}public string AppConfigPathAndName { get => appConfigPathAndName; }public string ParameterA { get => parameterA; }public string ParameterB { get => parameterB; }/// <summary>/// 读取配置文件,将配置文件中的内容读取出来设置到属性上/// </summary>private void ReadConfig(){List<string> configList= new List<string>();using (FileStream fs=new FileStream(appConfigPathAndName,FileMode.Open)){using (StreamReader sr=new StreamReader(fs)){string strLine=sr.ReadLine();while (!string.IsNullOrEmpty(strLine)){configList.Add(strLine);strLine = sr.ReadLine();}}if (configList!=null && configList.Count==2){parameterA = configList[0];parameterB = configList[1];}}}/// <summary>/// 创建配置文件/// </summary>private void CreateConfig(){if (File.Exists(appConfigPathAndName)) return;using (FileStream fs = new FileStream(appConfigPathAndName, FileMode.Create)){using (StreamWriter sw=new StreamWriter(fs)){sw.WriteLine("参数A");sw.WriteLine("参数B");sw.AutoFlush = true;}}}}//Class_end
}

 2、客户端使用

namespace SingletonPattern
{internal class Program{static void Main(string[] args){ReadAppConfig();Console.ReadLine();}/// <summary>/// 未使用任何模式读取配置文件/// </summary>private static void ReadAppConfig(){/*这里是直接使用new来实例化一个操作配置文件的对象AppConfig,会存在什么问题呢?*   若在系统运行的过程中,有很多地方都需要使用到这个配置内容,*   那么我们就要在很多地方创建AppConfig对象实例,此时系统就会存在多个AppConfig实例对象,*   这样会严重浪费内存资源;仔细看一下这些实例对象所包含的内容都是相同的*   (其实只需要一个实例就可以了),我们该如何实现呢?*/Console.WriteLine("未使用任何模式读取配置文件");AppConfig appConfig=new AppConfig();string str = $"配置文件中 parameterA={appConfig.ParameterA} parameterB={appConfig.ParameterB}";Console.WriteLine(str);Console.WriteLine($"未使用任何模式读取配置文件 实例对象{appConfig}的编号={appConfig.GetHashCode()}");AppConfig appConfig2 = new AppConfig();string str2 = $"配置文件中 parameterA={appConfig2.ParameterA} parameterB={appConfig2.ParameterB}";Console.WriteLine(str2);Console.WriteLine($"未使用任何模式读取配置文件 实例对象{appConfig2}的编号={appConfig2.GetHashCode()}");}}//Class_end
}

运行结果如下:

这里是直接使用new来实例化一个操作配置文件的对象AppConfig,会存在什么问题呢?
         若在系统运行的过程中,有很多地方都需要使用到这个配置内容, 那么我们就要在很多地方创建AppConfig对象实例,此时系统就会存在多个AppConfig实例对象, 这样会严重浪费内存资源;仔细看一下这些实例对象所包含的内容都是相同的(其实只需要一个实例就可以了),我们该如何实现呢?

 2.2、使用单例模式

        想要控制一个类只能被创建一个实例;那么首先要做的就是把创建实例的权限收回来,让类自己负责类实例的创建;然后再由这个类提供给外部可以获取该该类实例的方法。既然要收回创建实例的权限,那就需要将类的构造方法私有化。

  2.2.1、饿汉式单例

        所谓的饿汉式单例顾名思义:就是饿,一饿就比较着急,急需实例,所以一开始就直接创建类的实例。

1、如下是以读取配置文件类实现为【饿汉式】单例模式的写法:

/***
*	Title:"设计模式" 项目
*		主题:【饿汉式】单例模式(线程安全)
*	Description:
*	    基础概念:单例模式的本质【控制实例数目】
*	        单例模式:是用来保证这个类在运行期间只会被创建一个类实例;
*	                  单例模式还提供了一个全局唯一访问这个类实例的访问点(即Instance属性)
*	                  单例模式只关心类实例的创建问题,并不关心具体的业务功能
*	                  
*	        单例模式的范围:在C#中单例模式的范围是指在每个AppDomain之中只能存在一个实例的类
*	                        在Java中单例的范围是一个虚拟机的范围(因为装载类的功能是虚拟机,一个虚拟机通过自己的ClassLoader装载单例)
*	    
*	        单例模式的命名:建议使用GetInstance()作为单例模式的方法名;GetInstance()方法可以有参数
*	    
*	        单例模式的优点:
*	                    1、时间与空间
*	                    (懒汉式是典型的时间换空间【每次获取实例都会进行判断是否需要创建实例,浪费判断的时间,若一直没有人使用就不会创建实例,节约内存】
*	                      饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用先创建出来,每次调用时就不需要再判断了,节省运行时间)
*	                    2、线程安全
*	                    (饿汉式是线程安全的,因为在装载的时候只会装载一次,且在装载类的时候不会发生并发;
*	                      从线程安全性上来讲,不加同步的懒汉式线程是不安全的【即;有多个线程同时调用GetInstance方法就可能导致并发问题】)
*	        
*	        何时选用单例模式?
*	                    1、当需要控制类的实例只能有一个,且客户只能从一个全局访问点访问它
*	                   
*	Date:2025
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:***/using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SingletonPattern
{/// <summary>/// 【饿汉式】单例模式/// </summary>internal class AppConfig_HungrySingleton{//1、开始就定义一个变量来存储创建好的类实例private static AppConfig_HungrySingleton instance=new AppConfig_HungrySingleton();private string appConfigPathAndName = $"{Directory.GetCurrentDirectory()}\\AppConfig.txt";//存放配置文件中参数A的值private string parameterA;//存放配置文件中参数B的值private string parameterB;/// <summary>/// 2、私有化构造函数/// </summary>private AppConfig_HungrySingleton(){CreateConfig();ReadConfig();}//3、定义一个方法来为客户端提供AppConfig_HungrySingleton类的实例public static AppConfig_HungrySingleton GetInstance(){return instance;}public string AppConfigPathAndName { get => appConfigPathAndName; }public string ParameterA { get => parameterA; }public string ParameterB { get => parameterB; }/// <summary>/// 读取配置文件,将配置文件中的内容读取出来设置到属性上/// </summary>private void ReadConfig(){List<string> configList = new List<string>();using (FileStream fs = new FileStream(appConfigPathAndName, FileMode.Open)){using (StreamReader sr = new StreamReader(fs)){string strLine = sr.ReadLine();while (!string.IsNullOrEmpty(strLine)){configList.Add(strLine);strLine = sr.ReadLine();}}if (configList != null && configList.Count == 2){parameterA = configList[0];parameterB = configList[1];}}}/// <summary>/// 创建配置文件/// </summary>private void CreateConfig(){if (File.Exists(appConfigPathAndName)) return;using (FileStream fs = new FileStream(appConfigPathAndName, FileMode.Create)){using (StreamWriter sw = new StreamWriter(fs)){sw.WriteLine("参数A");sw.WriteLine("参数B");sw.AutoFlush = true;}}}}//Class_end
}

2、 客户端调用饿汉式单例

namespace SingletonPattern
{internal class Program{static void Main(string[] args){ReadAppConfig_HungrySingleton();Console.ReadLine();}/// <summary>/// 【饿汉式】单例模式读取配置文件(线程安全)/// </summary>private static void ReadAppConfig_HungrySingleton(){Console.WriteLine("\n【饿汉式】单例模式读取配置文件(线程安全)");AppConfig_HungrySingleton appConfig = AppConfig_HungrySingleton.GetInstance();string str = $"配置文件中 parameterA={appConfig.ParameterA} parameterB={appConfig.ParameterB}";Console.WriteLine(str);Console.WriteLine($"【饿汉式】单例模式读取配置文件(线程安全) 实例对象{appConfig}的编号={appConfig.GetHashCode()}");AppConfig_HungrySingleton appConfig2 = AppConfig_HungrySingleton.GetInstance();string str2 = $"配置文件中 parameterA={appConfig2.ParameterA} parameterB={appConfig2.ParameterB}";Console.WriteLine(str2);Console.WriteLine($"【饿汉式】单例模式读取配置文件(线程安全) 实例对象{appConfig2}的编号={appConfig2.GetHashCode()}");for (int i = 0; i <7; i++){Task task = Task.Run(() =>{AppConfig_HungrySingleton appConfigTask = AppConfig_HungrySingleton.GetInstance();Console.WriteLine($"【饿汉式】单例模式读取配置文件(线程安全)_{i} appConfig={appConfigTask.GetHashCode()}");});}}}//Class_end
}

运行结果如下:

  2.2.2、懒汉式单例

        所谓的懒汉式单例,顾名思义:懒,就是不着急,那么在创建对象实例的时候不会立即创建,会一直等到要使用对象实例时才会创建

平时我们使用到的缓存其实也是懒汉式思想(也叫延迟加载)的体现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SingletonPattern
{/// <summary>/// 单例模式的懒汉式还体现了缓存的思想/// 1、当某些资源或数据被频繁使用,而这些资源或数据存在系统外部(如数据库、硬盘文件等)每次操作///     这些数据的时候都得从数据库或磁盘上获取,速度会很慢,造成性能问题/// 2、一个简单的解决办法就是:把这些数据缓存到内存里面,每次操作的时候,先到内存里面找///     (看看是否存在这些数据,若有则直接使用;没有就获取它并设置到缓存中,///       下次访问就可以直接从内存获取,节省大量时间)缓存是一个种典型的空间换时间的方案/// </summary>internal class Cache{//定义缓存数据容器private Dictionary<string,object> _Dic=new Dictionary<string,object>();/// <summary>/// 从缓存中获取值/// </summary>/// <param name="key">键</param>/// <returns></returns>public object GetValue(string key){//先从缓存里面获取值object obj = _Dic[key];if (obj==null){//若缓存里面没有,那就去获取对应的数据(如读取数据库或磁盘文件获取)//我们这里仅作示意,虚拟一个值obj = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}_{new Random().Next(0,99)}";//将获取的值设置到缓存里面_Dic[key] = obj ;}//若有值则直接返回return obj;}}//Class_end
}

1、如下是以读取配置文件类实现为【懒汉式】单例模式的写法:

/***
*	Title:"设计模式" 项目
*		主题:【懒汉式】单例模式(线程不安全)
*	Description:
*	    基础概念:单例模式的本质【控制实例数目】
*	        单例模式:是用来保证这个类在运行期间只会被创建一个类实例;
*	                  单例模式还提供了一个全局唯一访问这个类实例的访问点(即Instance属性)
*	                  单例模式只关心类实例的创建问题,并不关心具体的业务功能
*	                  
*	        单例模式的范围:在C#中单例模式的范围是指在每个AppDomain之中只能存在一个实例的类
*	                        在Java中单例的范围是一个虚拟机的范围(因为装载类的功能是虚拟机,一个虚拟机通过自己的ClassLoader装载单例)
*	    
*	        单例模式的命名:建议使用GetInstance()作为单例模式的方法名;GetInstance()方法可以有参数
*	    
*	        单例模式的优点:
*	                    1、时间与空间
*	                    (懒汉式是典型的时间换空间【每次获取实例都会进行判断是否需要创建实例,浪费判断的时间,若一直没有人使用就不会创建实例,节约内存】
*	                      饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用先创建出来,每次调用时就不需要再判断了,节省运行时间)
*	                    2、线程安全
*	                    (饿汉式是线程安全的,因为在装载的时候只会装载一次,且在装载类的时候不会发生并发;
*	                      从线程安全性上来讲,不加同步的懒汉式线程是不安全的【即;有多个线程同时调用GetInstance方法就可能导致并发问题】)
*	                      
*	Date:2025
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:***/using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SingletonPattern
{/// <summary>/// 【懒汉式】单例模式/// </summary>internal class AppConfig_IdlerSingleton{//1、开始就定义一个变量来存储类实例(不立即创建实例)private static AppConfig_IdlerSingleton instance = null;private string appConfigPathAndName = $"{Directory.GetCurrentDirectory()}\\AppConfig.txt";//存放配置文件中参数A的值private string parameterA;//存放配置文件中参数B的值private string parameterB;/// <summary>/// 2、私有化构造函数/// </summary>private AppConfig_IdlerSingleton(){CreateConfig();ReadConfig();}//3、定义一个方法来为客户端提供AppConfig_IdlerSingleton类的实例public static AppConfig_IdlerSingleton GetInstance(){//4、判断存储实例的变量是否有值if (instance==null){//5、没有就创建一个类实例,并赋给存储类实例的变量instance = new AppConfig_IdlerSingleton();}//有值就直接返回return instance;}public string AppConfigPathAndName { get => appConfigPathAndName; }public string ParameterA { get => parameterA; }public string ParameterB { get => parameterB; }/// <summary>/// 读取配置文件,将配置文件中的内容读取出来设置到属性上/// </summary>private void ReadConfig(){List<string> configList = new List<string>();using (FileStream fs = new FileStream(appConfigPathAndName, FileMode.Open)){using (StreamReader sr = new StreamReader(fs)){string strLine = sr.ReadLine();while (!string.IsNullOrEmpty(strLine)){configList.Add(strLine);strLine = sr.ReadLine();}}if (configList != null && configList.Count == 2){parameterA = configList[0];parameterB = configList[1];}}}/// <summary>/// 创建配置文件/// </summary>private void CreateConfig(){if (File.Exists(appConfigPathAndName)) return;using (FileStream fs = new FileStream(appConfigPathAndName, FileMode.Create)){using (StreamWriter sw = new StreamWriter(fs)){sw.WriteLine("参数A");sw.WriteLine("参数B");sw.AutoFlush = true;}}}}//Class_end
}

 2、 客户端调用饿汉式单例

namespace SingletonPattern
{internal class Program{static void Main(string[] args){ReadAppConfig_IdlerSingleton();Console.ReadLine();}/// <summary>/// 【懒汉式】单例模式读取配置文件(线程不安全)/// </summary>private static void ReadAppConfig_IdlerSingleton(){Console.WriteLine("\n【懒汉式】单例模式读取配置文件(线程不安全)");AppConfig_IdlerSingleton appConfig = AppConfig_IdlerSingleton.GetInstance();string str = $"配置文件中 parameterA={appConfig.ParameterA} parameterB={appConfig.ParameterB}";Console.WriteLine(str);Console.WriteLine($"【懒汉式】单例模式读取配置文件(线程不安全) 实例对象{appConfig}的编号={appConfig.GetHashCode()}");AppConfig_IdlerSingleton appConfig2 = AppConfig_IdlerSingleton.GetInstance();string str2 = $"配置文件中 parameterA={appConfig2.ParameterA} parameterB={appConfig2.ParameterB}";Console.WriteLine(str2);Console.WriteLine($"【懒汉式】单例模式读取配置文件(线程不安全) 实例对象{appConfig2}的编号={appConfig2.GetHashCode()}");for (int i = 0; i < 7; i++){int tmp = new Random(DateTime.Now.GetHashCode()).Next(1, 4);Task task =new Task(() =>{Thread.Sleep(tmp);AppConfig_IdlerSingleton appConfigTask = AppConfig_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式读取配置文件(线程不安全)_{i} appConfig={appConfigTask.GetHashCode()}");});Thread.Sleep(1);task.Start();}Task task2 = Task.Run(() =>{AppConfig_IdlerSingleton appConfigTask2 = AppConfig_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式读取配置文件(线程不安全) appConfig2={appConfigTask2.GetHashCode()}");});}}//Class_end
}

运行结果如下:

        注意:懒汉式单例不加同步锁是【线程不安全的】 如:同时有两个线程A和B,它们同时调用GetInstance()方法,就有可能导致并发问题(即:会创建2个实例,导致单例控制在并发情况相爱失效),导致的情况如下图所示:

  2.2.3、懒汉式线程安全的单例

        那么该如何实现【懒汉式】单例的线程安全呢?我们可使用C#的【lock】锁控制;

lock 语句 - 同步对共享资源的访问 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/statements/lockAppDomain 类 (System) | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/api/system.appdomain?view=net-6.0托管线程处理基本知识 - .NET | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/standard/threading/managed-threading-basics基于任务的异步编程 - .NET | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/standard/parallel-programming/task-based-asynchronous-programming数据并行(任务并行库) - .NET | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/standard/parallel-programming/data-parallelism-task-parallel-library1、使用锁控制的【懒汉式】单例模式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SingletonPattern
{/// <summary>/// 线程安全的【饿汉式】单例(使用锁会耗费很多时间在线程同步上)/// </summary>internal class ThreadSafe_IdlerSingleton{//1、定义一个用于保存实例的静态变量private static ThreadSafe_IdlerSingleton instance;//2、定义一个保证线程同步的标识private static readonly object synchronized=new object();//3、私有构造函数(外界不能创建该类实例)private ThreadSafe_IdlerSingleton() { }//4、创建本类单例实例public static ThreadSafe_IdlerSingleton GetInstance(){//先检查实例是否存在,若不存在在加锁处理if (instance==null){//同步块,加锁处理lock (synchronized){//再次判断实例是否存在,不存在才创建if (instance == null){instance = new ThreadSafe_IdlerSingleton();}}}return instance;}}//Class_end
}

2、客户端调用

namespace SingletonPattern
{internal class Program{static void Main(string[] args){ThreadSafe_IdlerSingletonTest();Console.ReadLine();}/// <summary>/// 【懒汉式】单例模式(线程安全)/// </summary>private static void ThreadSafe_IdlerSingletonTest(){Console.WriteLine("\n【懒汉式】单例模式(线程安全)");Task task = new Task(() =>{ThreadSafe_IdlerSingleton threadSafe_IdlerSingleton1 = ThreadSafe_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式(线程安全) threadSafe_IdlerSingleton1的编号是:{threadSafe_IdlerSingleton1.GetHashCode()}");});Task task2 = new Task(() =>{ThreadSafe_IdlerSingleton threadSafe_IdlerSingleton2 = ThreadSafe_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式(线程安全) threadSafe_IdlerSingleton2的编号是:{threadSafe_IdlerSingleton2.GetHashCode()}");});Task task3 = new Task(() =>{ThreadSafe_IdlerSingleton threadSafe_IdlerSingleton3 = ThreadSafe_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式(线程安全) threadSafe_IdlerSingleton3的编号是:{threadSafe_IdlerSingleton3.GetHashCode()}");});task.Start();task2.Start();task3.Start();}}//Class_end
}

运行结果如下:

  2.2.4、优化版的懒汉式线程安全单例

静态类和静态类成员 - C# | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-membersstatic 修饰符 - C# reference | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/static1、实现不用锁的懒汉式线程安全单例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SingletonPattern
{/// <summary>/// 线程安全的【饿汉式】单例方案二(使用锁会耗费很多时间在线程同步上)/// </summary>internal class ThreadSafe2_IdlerSingleton{//1、私有化个构造方法private ThreadSafe2_IdlerSingleton() { }//2、定义一个没有与该类进行绑定的静态类,只有被调用时才会被装载,从而实现延迟加载private static class SingletonHolder{/** 静态初始化【即:只有这个类被装载并被初始化时,会初始化为静态域,从而创建ThreadSafe2_IdlerSingleton的实例】* 由于是静态域,因此只会在程序装载类时初始化一次,并由AppDomain来保证它的线程安全*/internal static readonly ThreadSafe2_IdlerSingleton instance = new ThreadSafe2_IdlerSingleton();}//3、创建本类的单例方法public static ThreadSafe2_IdlerSingleton GetInstance(){return SingletonHolder.instance;}}//Class_end
}

2、客户端调用

namespace SingletonPattern
{internal class Program{static void Main(string[] args){ThreadSafe2_IdlerSingletonTest();Console.ReadLine();}/// <summary>/// 【懒汉式】单例模式2(线程安全)/// </summary>private static void ThreadSafe2_IdlerSingletonTest(){Console.WriteLine("\n【懒汉式】单例模式2(线程安全)");Task task = new Task(() =>{ThreadSafe2_IdlerSingleton threadSafe2_IdlerSingleton1 = ThreadSafe2_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式2(线程安全) threadSafe2_IdlerSingleton1的编号是:{threadSafe2_IdlerSingleton1.GetHashCode()}");});Task task2 = new Task(() =>{ThreadSafe2_IdlerSingleton threadSafe2_IdlerSingleton2 = ThreadSafe2_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式2(线程安全) threadSafe2_IdlerSingleton2的编号是:{threadSafe2_IdlerSingleton2.GetHashCode()}");});Task task3 = new Task(() =>{ThreadSafe2_IdlerSingleton threadSafe2_IdlerSingleton3 = ThreadSafe2_IdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】单例模式2(线程安全) threadSafe2_IdlerSingleton3的编号是:{threadSafe2_IdlerSingleton3.GetHashCode()}");});task.Start();task2.Start();task3.Start();}}//Class_end
}

运行结果如下:

  2.2.5、可控制实例数量的线程安全单例模式

        单例模式是为了控制在运行期间,某些类的实例数目只能有一个;但有时候单个实例并不能满足需要,根据估算,设置为3个实例刚好,那如何实现控制的实例数为3个呢?我们可以借助容器来实现;至于实例的调度算法我们就不深究实现了:

1、编写可控制类实例数量的单例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace SingletonPattern
{/// <summary>/// 可控制实例数量的单例模式(线程安全)/// </summary>internal class ThreadSafe_MutiIdlerSingleton{//私有构造函数private ThreadSafe_MutiIdlerSingleton() { }//定义一个保证线程同步的标识private static readonly object synchronized = new object();//定义一个缺省的键前缀private static string defaultPreKey = "sn";//定义一个缓存实例的容器private static Dictionary<string, ThreadSafe_MutiIdlerSingleton> dic = new Dictionary<string, ThreadSafe_MutiIdlerSingleton>();//定义一个用来记录当前正在使用第几个实例,用以控制最大实例数量,到最大实例数量后,又从1开始private static int number = 1;//定一个控制实例的最大数量private static int maxNum = 3;public static ThreadSafe_MutiIdlerSingleton GetInstance(){string strKey=defaultPreKey+number;ThreadSafe_MutiIdlerSingleton instance = null;if (dic.ContainsKey(strKey)){instance = dic[strKey];}if (instance == null){//同步块,加锁处理lock (synchronized){//再次判断实例是否存在,不存在才创建if (instance == null && !dic.ContainsKey(strKey)){instance = new ThreadSafe_MutiIdlerSingleton();dic.TryAdd(strKey, instance);}else{instance = dic[strKey];}}}number++;if (number>maxNum){number = 1;}return instance;}}//Class_end
}

2、客户端测试

namespace SingletonPattern
{internal class Program{static void Main(string[] args){ThreadSafe_MutiIdlerSingletonTest();Console.ReadLine();}/// <summary>/// 【懒汉式】可控数量的单例模式(线程安全)/// </summary>private static void ThreadSafe_MutiIdlerSingletonTest(){Console.WriteLine("\n【懒汉式】可控数量的单例模式(线程安全)");for (int i = 0; i < 7; i++){Task task = Task.Run(() =>{Thread.Sleep(10);ThreadSafe_MutiIdlerSingleton threadSafe_MutiIdlerSingleton = ThreadSafe_MutiIdlerSingleton.GetInstance();Console.WriteLine($"【懒汉式】可控数量的单例模式(线程安全) threadSafe_MutiIdlerSingleton_{i}的编号是:{threadSafe_MutiIdlerSingleton.GetHashCode()}");});}}}//Class_end
}

运行结果如下:

三、项目源码工程

kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern

相关文章:

学习设计模式《四》——单例模式

一、基础概念 单例模式的本质【控制实例数目】&#xff1b; 单例模式的定义&#xff1a;是用来保证这个类在运行期间只会被创建一个类实例&#xff1b;单例模式还提供了一个全局唯一访问这个类实例的访问点&#xff08;即GetInstance方法&#xff09;单例模式只关心类实例的创建…...

构建具备推理与反思能力的高级 Prompt:LLM 智能代理设计指南

在构建强大的 AI 系统&#xff0c;尤其是基于大语言模型&#xff08;LLM&#xff09;的智能代理&#xff08;Agent&#xff09;时&#xff0c;Prompt 设计的质量决定了系统的智能程度。传统 Prompt 通常是简单的问答或填空式指令&#xff0c;而高级任务需要更具结构性、策略性和…...

NLP 梳理03 — 停用词删除和规范化

一、说明 前文我们介绍了标点符号删除、文本的大小写统一&#xff0c;本文介绍英文文章的另一些删除内容&#xff0c;停用词删除。还有规范化处理。 二、什么是停用词&#xff0c;为什么删除它们&#xff1f; 2.1 停用词的定义 停用词是语言中的常用词&#xff0c;通常语义…...

算法—插入排序—js(小数据或基本有序数据)

插入排序原理&#xff1a;&#xff08;适合小规模数据&#xff09; 将数组分为“已排序”和“未排序”两部分&#xff0c;逐个将未排序元素插入到已排序部分的正确位置。 特点&#xff1a; 时间复杂度&#xff1a;平均 O(n)&#xff0c;最优&#xff08;已有序&#xff09;O(n…...

家庭电脑隐身后台自动截屏软件,可远程查看

7-4 本文介绍一个小软件&#xff0c;可以在电脑后台运行&#xff0c;并且记录电脑的屏幕画面保存下来&#xff0c;并且可以远程提取查看。 可以用于记录长时间运行的软件的执行画面过程&#xff0c;或者用于记录家庭中小孩使用电脑的过程&#xff0c;如果没有好好上网课&…...

【Agent】AI智能体评测基座AgentCLUE-General

note AgentCLUE-General将题目划分为“联网检索”、“数据分析”、“多模态理解”和“多场景组合”任务AgentCLUE-General为每个题目都提供一个标准答案&#xff0c;将Agent智能体的答案与标准答案进行规则匹配判断对错 文章目录 note一、任务划分和场景划分二、答案提取的pro…...

最新iOS性能测试方法与教程

一、工具instrument介绍 使用Xcode的instrument进行测试&#xff0c;instrument自带了很多性能方面的测试工具&#xff0c;如图所示&#xff1a; 二、常见性能测试内容 不管是安卓还是iOS的性能测试&#xff0c;常见的性能测试都要包含这五个方面&#xff1a; 1、内存&#xff…...

多模态大语言模型arxiv论文略读(三十)

Mastering Text-to-Image Diffusion: Recaptioning, Planning, and Generating with Multimodal LLMs ➡️ 论文标题&#xff1a;Mastering Text-to-Image Diffusion: Recaptioning, Planning, and Generating with Multimodal LLMs ➡️ 论文作者&#xff1a;Ling Yang, Zhao…...

【AI论文】CLIMB:基于聚类的迭代数据混合自举语言模型预训练

摘要&#xff1a;预训练数据集通常是从网络内容中收集的&#xff0c;缺乏固有的领域划分。 例如&#xff0c;像 Common Crawl 这样广泛使用的数据集并不包含明确的领域标签&#xff0c;而手动整理标记数据集&#xff08;如 The Pile&#xff09;则是一项劳动密集型工作。 因此&…...

AI大模型发展现状与MCP协议诞生的技术演进

1. 大模型能力边界与用户痛点&#xff08;2023年&#xff09; 代表模型&#xff1a;GPT-4&#xff08;OpenAI&#xff09;、Claude 3&#xff08;Anthropic&#xff09;、通义千问&#xff08;阿里云&#xff09;等展现出强大的生成能力&#xff0c;但存在明显局限&#xff1a…...

从malloc到free:动态内存管理全解析

1.为什么要有动态内存管理 我们已经掌握的内存开辟方法有&#xff1a; int main() {int val 20;//在栈空间上开辟四个字节char arr[20] { 0 };//在栈空间上开辟10个字节的连续空间return 0; }上述开辟的内存空间有两个特点&#xff1a; 1.空间开辟的时候大小已经固定 2.数组…...

CSS值和单位

CSS值和单位 CSS 中的值和单位是构建样式的基础&#xff0c;它们定义了属性的具体表现方式。值用于定义样式属性的具体取值&#xff0c;而单位用于指定这些值的度量方式。CSS中常用的值和单位如下&#xff1a; 1.长度单位 px : 像素&#xff0c;绝对单位 em : 相对于元素的字…...

Redis高级篇之I/O多路复用的引入解析

文章目录 一、问题背景1. 高并发连接的管理2. 避免阻塞和延迟3. 减少上下文切换开销4. 高效的事件通知机制5. 简化编程模型6. 低延迟响应本章小节 二、I/O多路复用高性能的本质1. 避免无意义的轮询&#xff1a;O(1) 事件检测2. 非阻塞 I/O 零拷贝&#xff1a;最大化 CPU 利用率…...

FTP协议命令和响应码

文章目录 &#x1f4e6; 一、什么是 FTP 协议&#xff1f;&#x1f9fe; 二、FTP 常见命令&#xff08;客户端发送&#xff09;&#x1f4e1; 三、FTP 响应码&#xff08;服务端返回&#xff09;&#x1f4cc; 响应码分类&#xff08;第一位&#xff09;✅ 常见成功响应码&…...

在win上安装Ubuntu安装Anaconda(linx环境)

一&#xff0c;安装Ubuntu 1. 在 Microsoft 商城去下载Ubuntu(LTS:是长期维护的版本) 2.安装完之后启动程序&#xff0c;再重新打开一个黑窗口&#xff1a; wsl --list --verbose 3.关闭Ubuntu wsl --shutdown Ubuntu-22.04 WSL2 Ubuntu-20.04文件太占c盘空间&#xff0c;…...

【Elasticsearch入门到落地】11、RestClient初始化索引库

接上篇《10、初始化RestClient》 上一篇我们已经完成了RestHighLevelClient的初始化工作&#xff0c;本篇将正式进入索引库的创建阶段。我们将使用Java代码来创建酒店数据的索引库。 一、准备工作 1. 创建常量类 首先&#xff0c;我们需要定义一个常量类来存放索引库的mappi…...

远程服务调用的一些注意事项

引言 最近工作中&#xff0c;遇到了一些关于远程服务调用的问题&#xff0c;背景是调用三方接口获取某些特征数据&#xff0c;但由于调用出现了超时&#xff0c;导致业务本身的接口的可用行降低。因此整理一些远程服务调用时的注意事项&#xff0c;通过不同维度的考虑来提高系…...

QML 样式库

在 QML 中&#xff0c;样式库&#xff08;或 UI 框架&#xff09;用于快速构建一致且美观的界面。Qt/QML 本身不提供内置的完整样式库&#xff0c;但可以通过以下方式实现样式管理或使用第三方库。 1. Qt Quick Controls 2 样式系统 Qt Quick Controls 2 是官方提供的 UI 组件…...

[RHEL8] 指定rpm软件包的更高版本模块流

背景&#xff1a;挂载RHEL ISO使用kickstart安装操作系统&#xff0c;安装包未指定安装perl&#xff0c;但是安装完可以查到其版本&#xff0c;且安装的是ISO中多个版本中的最低版本。 原因&#xff1a;&#xff08;1&#xff09;为什么没有装perl&#xff0c;perl -v可以看到版…...

使用Python可视化洛伦兹变换

引言 大家好!今天我们将探讨一个非常有趣且重要的物理概念—洛伦兹变换。它是相对论的核心内容之一,描述了在高速运动下,时间、长度以及其他物理量是如何发生变化的。通过使用 Python 进行可视化,我们不仅可以更好地理解这个概念,还能感受到物理世界中的奇妙之处。 什么…...

【二叉树专题】一道深入浅出的 DFS 题:求二叉树的直径(含通俗易懂讲解)

题目&#xff1a; 给你一棵二叉树的根节点&#xff0c;返回这棵树的 直径。 直径 是任意两个节点路径中&#xff0c;最长的一条路径所经过的边数。 比如下面这棵树&#xff1a; 1/ \2 3/ \ 4 5它的最长路径是&#xff1a;4 → 2 → 5 或者 4 → 2 → 1 → 3&#xff0c…...

考研系列-计算机网络-第三章、数据链路层

一、数据链路层的功能 1.知识点总结 2.习题总结...

医药采购系统平台第10天02:按药品分类的统计按供货商统计按医院统计统计数据的导出DWR的配置和应用

如果想要获取相关的源码&#xff0c;笔记&#xff0c;和相关工具&#xff0c;对项目需求的二次开发&#xff0c;可以关注我并私信&#xff01;&#xff01;&#xff01; 一 按药品分类的统计实现 1 按药品分类统计的需求 按药品统计&#xff1a;在指定时间段中采购量、采购金…...

Navicat、DataGrip、DBeaver在渲染 BOOLEAN 类型字段时的一种特殊“视觉风格”

文章目录 前言✅ 为什么 Boolean 字段显示为 [ ]&#xff1f;✅ 如何验证实际数据类型&#xff1f;✅ 小结 前言 看到的 deleted: [ ] 并不是 Prisma 的问题&#xff0c;而是数据库客户端&#xff08;如 Navicat、DataGrip、DBeaver&#xff09;在渲染 BOOLEAN 类型字段时的一种…...

(undone) 吴恩达版提示词工程 2. 指南

url: https://www.bilibili.com/video/BV1Z14y1Z7LJ?spm_id_from333.788.videopod.episodes&vd_source7a1a0bc74158c6993c7355c5490fc600&p2 别人的笔记 url: https://zhuanlan.zhihu.com/p/626966526 指导原则&#xff08;Guidelines&#xff09; 编写提示词有两个…...

VLC搭建本机的rtsp直播推流和拉流

媒体---流---捕获设备&#xff0c;选择摄像头&#xff0c;点击串流 x下一步 选择rtsp&#xff0c;点击添加 看到了端口&#xff0c;并设置路径&#xff1a; 选择Video -H 264 mp3(TS) 点击下一个&#xff0c; 点击流&#xff0c;就开始推流了 拉流&#xff0c;观看端&#x…...

Rocky Linux 9.1 修改网卡和DNS

在 Rocky Linux 9.1 中修改网卡和 DNS 配置可以通过 NetworkManager 工具实现(推荐)或直接编辑配置文件。以下是两种方法的详细步骤: 方法一:使用 nmcli 命令行工具(动态生效) 查看当前网络连接nmcli connection show # 输出示例: # NAME UUID …...

Web前端:常用的布局属性

常见的布局方式有哪些&#xff1f; float&#xff1a;浮动布局 ​position 定位布局 ​flex 弹性布局&#xff08;display&#xff09; ​table 表格布局&#xff08;弃用&#xff09; 一、HTML5 语义化布局标签 这些标签本身不提供布局能力&#xff0c;但能增强页面结构…...

XSS学习2

一、客户端的Cookie 1. 无状态的影响 无状态问题: HTTP协议的无状态特性导致每次请求都是独立的&#xff0c;无法保持会话。例如&#xff0c;在银行办理业务时&#xff0c;柜员不需要重复询问客户信息&#xff0c;但在计算机网络中&#xff0c;每次HTTP请求都需要重新认证用户…...

软件设计师/系统架构师---计算机网络

概要 什么是计算机网络&#xff1f; 计算机网络是指将多台计算机和其他设备通过通信线路互联&#xff0c;以便共享资源和信息的系统。计算机网络可以有不同的规模&#xff0c;从家庭网络到全球互联网。它们可以通过有线&#xff08;如以太网&#xff09;或无线&#xff08;如W…...

Kubernetes(k8s)学习笔记(二)--k8s 集群安装

1、kubeadm kubeadm 是官方社区推出的一个用于快速部署 kubernetes 集群的工具。这个工具能通过两条指令完成一个 kubernetes 集群的部署&#xff1a; 1.1 创建一个 Master 节点$ kubeadm init 1.2 将一个 Node 节点加入到当前集群中$ kubeadm join <Master 节点的 IP 和…...

线性DP:最长上升子序列(子序列可不连续,子数组必须连续)

目录 Q1&#xff1a;简单遍历 Q2&#xff1a;变式&#xff08;加大数据量&#xff09; Q1&#xff1a;简单遍历 Dp问题 状态表示 f(i,j) 集合所有以第i个数结尾的上升子序列集合-f(i,j)的值存的是什么序列长度最大值max- 状态计算 &#xff08;其实质是集合的划分&#xff09;…...

SpringBoot 基本原理

SpringBoot 为我们做的自动配置&#xff0c;确实方便快捷&#xff0c;但一直搞不明白它的内部启动原理&#xff0c;这次就来一步步解开 SpringBoot 的神秘面纱&#xff0c;让它不再神秘。 目录 SpringBootApplication 背后的秘密 Configuration ComponentScan EnableAutoC…...

LeetCode第158题_用Read4读取N个字符 II

LeetCode 第158题&#xff1a;用Read4读取N个字符 II 题目描述 给你一个文件&#xff0c;并且该文件只能通过给定的 read4 方法来读取&#xff0c;请实现一个方法来读取 n 个字符。 read4 方法&#xff1a; API read4 可以从文件中读取 4 个连续的字符&#xff0c;并且将它…...

webgl入门实例-矩阵在图形学中的作用

矩阵在图形学中扮演着核心角色&#xff0c;几乎所有图形变换、投影和空间转换都依赖矩阵运算来实现高效计算。以下是矩阵在图形学中的主要作用及具体应用&#xff1a; 1. 几何变换 矩阵乘法可以高效表示物体的平移、旋转、缩放等基本变换&#xff0c;并通过矩阵连乘实现复合变…...

基于Matlab求解矩阵电容等效容值

1需求 仿真测试8*10阶举证电容等效容值。 2模型搭建 2.1打开simscape 在打开simulink之后打开simscape库&#xff0c;Simscape库位置如下 2.2搭建模型 在库中寻找需要的元件搭建电路。 2.2.1基本元件 电阻电容电感等基础器件&#xff0c;搭建电路之后需要对其进行幅值&…...

铅酸电池充电器方案EG1253+EG4321

参考&#xff1a; 基于EG1253EG4321铅酸电池(48V20AH)三段式充电器 屹晶微高性价比的电瓶车充电器方案——EG1253 电瓶电压 48V电瓶锂电池&#xff0c;其充满约为55V~56V&#xff0c;因此充电器输出电压为55V~56V&#xff1b; 若是48V铅酸电池&#xff0c;标称电压为48V&…...

每天学一个 Linux 命令(26):less

​​可访问网站查看,视觉品味拉满: http://www.616vip.cn/26/index.html less 是 Linux 中一个强大的文件内容查看工具,用于分页显示文件内容,支持快速搜索、滚动浏览、跳转等操作。相比 more,less 功能更丰富且支持向前和向后翻页,适合查看大文件或日志。 命令格式 les…...

【网络】数据链路层知识梳理

全是通俗易懂的讲解&#xff0c;如果你本节之前的知识都掌握清楚&#xff0c;那就速速来看我的笔记吧~ 自己写自己的八股&#xff01;让未来的自己看懂&#xff01; &#xff08;全文手敲&#xff0c;受益良多&#xff09; 数据链路层 我们来重新理解一下这个图&#xff1a;…...

2.2 BackgroundWorker的使用介绍

BackgroundWorker 是 .NET Framework 中一个简化异步操作的组件&#xff0c;它位于 System.ComponentModel 命名空间下。它为开发人员提供了一种简单的方式在后台执行耗时操作&#xff0c;同时保持与 UI 线程的交互 主要属性以及任务如下&#xff1a; DoWork 事件&#xff1a;…...

Java从入门到“放弃”(精通)之旅——类和对象全面解析⑦

Java从入门到“放弃”&#xff08;精通&#xff09;之旅&#x1f680;——类和对象全面解析⑦ 一、面向对象初探 1.1 什么是面向对象&#xff1f; Java是一门纯面向对象的语言(OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&a…...

无回显RCE

在CTF和实战渗透中&#xff0c;不是每一个命令执行点都有回显&#xff0c;有时我们审了半天代码&#xff0c;却发现好不容易找到的命令执行没有回显&#xff0c;但是这并不代表这段代码不能被我们利用&#xff0c;在无回显的情况下也是可以利用的 首先我们来写一个最简单的php…...

DQN在Gym的MountainCar环境的实现

DQN on MountainCar 引言 在本次实验里&#xff0c;我构建了DQN和Dueling DQN&#xff0c;并在Gymnasium库的MountainCar环境中对它们展开测试。我通过调整训练任务的超参数&#xff0c;同时设计不同的奖励函数及其对应参数&#xff0c;致力于获取更优的训练效果。最后&#…...

typescript判断是否为空

1 判断数据类型 1.1 基础数据类型 比如number&#xff0c;string&#xff0c;boolean&#xff0c;使用typeof&#xff0c;返回值是string类型&#xff1a; 例如&#xff1a; if("number" typeof(item)) {egret.log("item的类型是number"); } else if(&…...

JavaScript forEach介绍(JS forEach、JS for循环)

文章目录 JavaScript forEach 方法全面解析基本概念语法详解参数说明 工作原理与其他循环方法的比较forEach vs for循环forEach vs map 实际应用场景DOM元素批量操作数据处理 性能考量常见陷阱与解决方案无法中断循环异步操作问题 高级技巧链式调用&#xff08;不使用 forEach …...

C语言之图像文件的属性

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 图像文件属性提取系统设计与实现 目录 设计题目设计内容系统分析总体设计详细设计程序实现…...

Java链表反转方法详解

一、理解链表结构 假设链表节点定义为&#xff1a; class ListNode {int val;ListNode next;ListNode(int x) { val x; } } 二、迭代法反转链表 核心思路 逐步反转每个节点的指针方向&#xff0c;最终使整个链表反向。 步骤拆解 初始化三个指针&#xff1a; prev&#xf…...

lmm-r1开源程序是扩展 OpenRLHF 以支持 LMM RL 训练,用于在多模态任务上重现 DeepSeek-R1

一、软件介绍 文末提供程序和源码下载学习 lmm-r1开源程序是扩展 OpenRLHF 以支持 LMM RL 训练&#xff0c;用于在多模态任务上重现 DeepSeek-R1。 二、简介 小型 3B 大型多模态模型&#xff08;LMMs&#xff09;由于参数容量有限以及将视觉感知与逻辑推理相结合的固有复杂性…...

Java学习笔记(数组,方法)

一&#xff0c;数组 1.数组初始化 1.1动态初始化 格式&#xff1a;数据类型[] 数组名 new 数据类型[数组长度]; int[] arr new int[3]; // 定义长度为3的int数组&#xff0c;元素默认值为0 double[] scores new double[5]; // 长度5&#xff0c;元素默认0.0 String[…...

嵌入式---零点漂移(Zero Drift)

一、零点漂移的定义与本质 零点漂移&#xff08;简称“零漂”&#xff09;指传感器在输入信号为零&#xff08;或理论上应输出固定零值&#xff09;时&#xff0c;输出信号随时间、温度、环境等因素变化而偏离初始零点的现象。 核心特征&#xff1a;无输入时输出非零且缓慢变…...