學習設計模式《四》——單例模式

一、基礎概念

????????單例模式的本質【控制實例數目】;

????????單例模式的定義:是用來保證這個類在運行期間只會被創建一個類實例;單例模式還提供了一個全局唯一訪問這個類實例的訪問點(即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.pswp.cn/diannao/79869.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/79869.shtml
英文地址,請注明出處:http://en.pswp.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…