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

发布于:2025-04-21 ⋅ 阅读:(25) ⋅ 点赞:(0)

一、基础概念

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

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


网站公告

今日签到

点亮在社区的每一天
去签到