一、基礎概念
????????單例模式的本質【控制實例數目】;
????????單例模式的定義:是用來保證這個類在運行期間只會被創建一個類實例;單例模式還提供了一個全局唯一訪問這個類實例的訪問點(即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 Learn
https://learn.microsoft.com/zh-cn/dotnet/api/system.appdomain?view=net-6.0托管線程處理基本知識 - .NET | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/standard/threading/managed-threading-basics基于任務的異步編程 - .NET | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/standard/parallel-programming/task-based-asynchronous-programming數據并行(任務并行庫) - .NET | Microsoft Learn
https://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 Learn
https://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