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

一、基础概念

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

        单例模式的定义:是用来保证这个类在运行期间只会被创建一个类实例;单例模式还提供了一个全局唯一访问这个类实例的访问点(即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

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

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

相关文章

零基础上手Python数据分析 (19):Matplotlib 高级图表定制 - 精雕细琢,让你的图表脱颖而出!

写在前面 —— 超越默认样式,掌握 Matplotlib 精细控制,打造专业级可视化图表 上一篇博客,我们学习了 Matplotlib 的基础绘图功能,掌握了如何绘制常见的折线图、柱状图、散点图和饼图,并进行了基本的图表元素定制,例如添加标题、标签、图例等。 这些基础技能已经能让我…

信奥中的数学

信奥赛的数学大纲 ps:知识点是其他小伙伴分享的&#xff0c;我现在在做一下系列视频 会逐步更新&#xff0c;希望大家支持喜欢。 1.基础数学 数论 整数和自然数 素数、合数和因数分解 最大公约数(GCD)和最小公倍数(LCM) 同余和取模运算 欧几里得算法 扩展欧几里得算法 中国…

PHP腾讯云人脸核身获取Access Token

参考腾讯云官方文档&#xff1a; 人脸核身 获取 Access Token_腾讯云 public function getAccessToken(){$data [appId > , //WBappid,https://cloud.tencent.com/document/product/1007/49634secret > ,grant_type > client_credential, //授权类型version > 1…

《作用域大冒险:从闭包到内存泄漏的终极探索》

“爱自有天意&#xff0c;天有道自不会让有情人分离” 大家好&#xff0c;关于闭包问题其实实际上是js作用域的问题&#xff0c;那么js有几种作用域呢&#xff1f; 作用域类型关键字/场景作用域范围示例全局作用域var&#xff08;无声明&#xff09;整个程序var x 10;函数作用…

为什么Makefile中的clean需要.PHONY

原因一&#xff1a;避免Makefile检查时间戳 前置知识&#xff1a;makefile在依赖文件没有改变时不会执行编译命令 #第一次执行&#xff0c;OK [rootVM-16-14-centos ~]# make g -E main.cc -o main.i g -S main.i -o main.s g -c main.s -o main.o g main.o -o main#第二…

垂直行业突围:工业软件在汽车、航空领域的 “破壁” 实践

在当今科技高速发展的时代&#xff0c;工业软件已悄然完成从通用工具到垂直行业 “战略武器” 的蜕变。特别是在汽车与航空这两大高端制造领域&#xff0c;工业软件的价值早已超越单纯的效率提升&#xff0c;成为关乎核心技术自主可控的关键要素&#xff0c;一场围绕工业软件的…

07.Python代码NumPy-排序sort,argsort,lexsort

07.Python代码NumPy-排序sort&#xff0c;argsort&#xff0c;lexsort 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是NumPy的使用语法。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性&#xff0c;希望…

LVDS系列8:Xilinx 7系可编程输入延迟(一)

在解析LVDS信号时&#xff0c;十分重要的一环就是LVDS输入信号线在经过PCB输入到FPGA中后&#xff0c;本来该严格对齐的信号线会出现时延&#xff0c;所以需要在FPGA内部对其进行延时对齐后再进行解析。 Xilinx 7系器件中用于输入信号延时的组件为IDELAYE2可编程原语&#xff0…

AI驱动研发效率在中后台的实践

本文探讨了AI驱动的中后台前端研发实践&#xff0c; 涵盖设计出码、接口定义转换、代码拟合、自动化测试等多个环节&#xff0c;通过具体案例展示了AI技术如何优化研发流程并提升效率。特别是在UI代码编写和接口联调阶段&#xff0c;并提出了设计出码&#xff08;Design to Cod…

【Rust 精进之路之第6篇-流程之舞】控制流:`if/else`, `loop`, `while`, `for` 与模式匹配初窥

系列: Rust 精进之路:构建可靠、高效软件的底层逻辑 作者: 码觉客 发布日期: 2025-04-20 引言:让代码“活”起来——指令的流动 在前面的文章中,我们已经掌握了 Rust 的基础数据类型(标量和复合类型)以及如何通过变量绑定来存储和命名它们。这相当于我们准备好了程序…

C++ 表达式求值的基础(四十九)

1. 运算符的分类 1.1 按操作数个数 一元运算符&#xff08;Unary&#xff09; 作用于单个操作数&#xff1a; 取地址 &obj解引用 *ptr逻辑非 !b一元加减 x, -x递增递减 i, i-- 二元运算符&#xff08;Binary&#xff09; 作用于两个操作数&#xff1a; 算术运算 a b, a …

Three.js + React 实战系列 : 从零搭建 3D 个人主页

可能你对tailiwindcss毫不了解&#xff0c;别紧张&#xff0c;记住我们只是在学习&#xff0c;学习的是作者的思想和技巧&#xff0c;并不是某一行代码。 在之前的几篇文章中&#xff0c;我们已经熟悉了 Three.js 的基本用法&#xff0c;并通过 react-three-fiber 快速构建了一…

Kotlin实现Android应用保活方案

Kotlin实现Android应用保活优化方案 以下的Android应用保活实现方案&#xff0c;更加符合现代Android开发规范&#xff0c;同时平衡系统限制和用户体验。 1. 前台服务方案 class OptimizedForegroundService : Service() {private val notificationId 1private val channel…

windows拷贝文件脚本

1、新建脚本文件xxx.bat&#xff0c;名字任意&#xff0c;后缀未.bat即可&#xff0c;将以下内容拷贝进去&#xff0c;修改src和des为自己文件的目录即可。 echo off :: 设置字符集为UTF-8&#xff0c;命令窗口能正确显示中文字符。 chcp 65001 rem 读取当前目录并进入当前目…

Qt 核心库总结

Qt 核心库&#xff08;QtCore&#xff09; QtCore 是 Qt 框架的基础模块&#xff0c;提供非图形界面的核心功能&#xff0c;是所有 Qt 应用程序的基石。它包含事件循环、信号与槽、线程管理、文件操作、字符串处理等功能&#xff0c;适用于 GUI 和非 GUI 应用程序。本文将从入…

大模型相关面试问题原理及举例

大模型相关面试问题原理及举例 目录 大模型相关面试问题原理及举例Transformer相关面试问题原理及举例大模型模型结构相关面试问题原理及举例注意力机制相关面试问题原理及举例大模型与传统模型区别 原理:大模型靠海量参数和复杂结构,能学习更复杂模式。传统模型参数少、结构…

【AI+HR实战应用】用DeepSeek提升HR工作效能

用DeepSeek提升HR工作效能 一、AI 与 AIGC 简介二、DeepSeek 介绍三、使用 DeepSeek 的渠道及硬件要求四、使用 DeepSeek 的核心技巧五、AI 在人力资源的应用场景六、AI 绘画与多模态应用七、个人使用 AI 的能力层级八、企业拥抱 AI 的策略九、提示词管理的重要性 一、AI 与 AI…

Postgresql几个常用的json操作

将行记录转为jsonb row_to_json(表名或别名)将行记录集转为json数组 &#xff08;jsonb) select json_agg(row_to_json(t) order by t.task_name) into v_next_taskfrom dyna_flow_task t where t.zidv_template_id and t.levelv_next_level ;访问json字段&#xff0c;用->…

ESP32学习与快速总结——5.系统存储

1.ESP32分区表 为什么ESP32要分区 00&#xff1a;34-- 简述&#xff1a;其他单片机生成文件少&#xff0c;功能少&#xff0c;而ESP32功能多&#xff0c;文件多 分区表各个文件简介 --7&#xff1a;31vscode查看分区表 --9&#xff1a;33ota通过idf.py menuconfi…

Linux 进程控制(自用)

非阻塞调用waitpid 这样父进程就不会阻塞&#xff0c;此时循环使用我们可以让父进程执行其他任务而不是阻塞等待 进程程序替换 进程PCB加载到内存中的代码和数据 替换就是完全替换当前进程的代码段、数据段、堆和栈&#xff0c;保存当前的PCB 代码指的是二进制代码不是源码&a…