一、什么是单例模式 【单例模式】,英文名称:Singleton Pattern,这个模式很简单,一个类型只需要一个实例,他是属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)。
那咱们大概知道了,其实说白了,就是我们整个项目周期内,只会有一个实例,当项目停止的时候,实例销毁,当重新启动的时候,我们的实例又会产品。 上文中说到了一个名词【创建类型】的设计模式,那什么是创建类型的设计模式呢? 创建型(Creational)模式:负责对象创建,我们使用这个模式,就是为了创建我们需要的对象实例的。 那除了创建型还有其他两种类型的模式:
咱们就重点从0开始分析分析如何创建一个单例模式的对象实例。 二、如何创建单例模式实现单例模式有很多方法:从“懒汉式”到“饿汉式”,最后“双检锁”模式。 这里咱们就慢慢的,从一步一步的开始讲解如何创建单例,既然要创建单一的实例,那我们首先需要学会如何去创建一个实例,这个很简单,相信每个人都会创建实例,就比如说这样的: /// <summary> /// 定义一个天气类 /// </summary> public class WeatherForecast { public DateTime Date { get; set; } = DateTime.Now; public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string Summary { get; set; } } [HttpGet]public WeatherForecast Get(){ // 实例化一个对象实例 WeatherForecast weather = new WeatherForecast(); return weather;} 我们每次访问的时候,时间都是会变化,所以我们的实例也是一直在创建,在变化, 相信每个人都能看到这个代码是什么意思,不多说,直接往下走,我们知道,单例模式的核心目的就是:
那好,我们改进改进,不是说要唯一一个么,好说!我直接返回不就行了: /// <summary> /// 定义一个天气类 /// </summary> public class WeatherForecast { // 定义一个静态变量来保存类的唯一实例 private static WeatherForecast uniqueInstance; 然后我们修改一下调用方法,因为我们的默认构造函数已经私有化了,不允许再创建实例了,所以我们直接这么调用: [HttpGet] public WeatherForecast Get() { // 实例化一个对象实例 WeatherForecast weather = WeatherForecast.GetInstance(); return weather; } 最后来看看效果: 这个时候,我们可以看到,时间已经不发生变化了,也就是说我们的实例是唯一的了,大功告成!是不是很开心! 但是,别着急,问题来了,我们目前是单线程的,所以只有一个,那如果多线程呢,如果多个线程同时访问,会不会也会正常呢? 这里我们做一个测试,我们在项目启动的时候,用多线程去调用: public WeatherForecast Get() { // 实例化一个对象实例 //WeatherForecast weather = WeatherForecast.GetInstance(); // 多线程去调用 for (int i = 0; i < 3; i++) { var th = new Thread( new ParameterizedThreadStart((state) => { WriteWeather(); }) ); th.Start(i); } return null; } 然后我们看看效果是怎样的,按照我们的思路,应该是只会走一遍构造函数,其实不是: 3个线程在第一次访问GetInstance方法时,同时判断(uniqueInstance ==null)这个条件时都返回真,然后都去创建了实例,这个肯定是不对的。那怎么办呢,只要让GetInstance方法只运行一个线程运行就好了,我们可以加一个锁来控制他,代码如下: public class WeatherForecast { // 定义一个静态变量来保存类的唯一实例 private static WeatherForecast uniqueInstance; // 定义一个锁,防止多线程 private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例 private WeatherForecast() { } /// <summary> /// 静态方法,来返回唯一实例 /// 如果存在,则返回 /// </summary> /// <returns></returns> public static WeatherForecast GetInstance() { // 当第一个线程执行的时候,会对locker对象 '加锁', // 当其他线程执行的时候,会等待 locker 执行完解锁 lock (locker) { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new WeatherForecast(); } } return uniqueInstance; } public DateTime Date { get; set; } = DateTime.Now; public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string Summary { get; set; } } 这个时候,我们再并发测试,发现已经都一样了,这样就达到了我们想要的效果,但是这样真的是最完美的么,其实不是的,因为我们加锁,只是第一次判断是否为空,如果创建好了以后,以后就不用去管这个 lock 锁了,我们只关心的是 uniqueInstance 是否为空,那我们再完善一下: /// <summary> /// 定义一个天气类 /// </summary> public class WeatherForecast { // 定义一个静态变量来保存类的唯一实例 private static WeatherForecast uniqueInstance; // 定义一个锁,防止多线程 private static readonly object locker = new object(); 这样才最终的完美实现我们的单例模式!搞定。 三、我们在哪里遇到过?如果你看过我的 Blog.Core 项目的话,肯定看到过 Redis 那部分,我在那里就是封装了一个单例,感兴趣的可以看看: public class RedisCacheManager : IRedisCacheManager {
private readonly string redisConnenctionString; public volatile ConnectionMultiplexer redisConnection; private readonly object redisConnectionLock = new object(); public RedisCacheManager() { string redisConfiguration = Appsettings.app(new string[] { 'AppSettings', 'RedisCachingAOP', 'ConnectionString' });//获取连接字符串 if (string.IsNullOrWhiteSpace(redisConfiguration)) { throw new ArgumentException('redis config is empty', nameof(redisConfiguration)); } this.redisConnenctionString = redisConfiguration; this.redisConnection = GetRedisConnection(); } /// <summary> /// 核心代码,获取连接实例 /// 通过双if 夹lock的方式,实现单例模式 /// </summary> /// <returns></returns> private ConnectionMultiplexer GetRedisConnection() { //如果已经连接实例,直接返回 if (this.redisConnection != null && this.redisConnection.IsConnected) { return this.redisConnection; } //加锁,防止异步编程中,出现单例无效的问题 lock (redisConnectionLock) { if (this.redisConnection != null) { //释放redis连接 this.redisConnection.Dispose(); } try { this.redisConnection = ConnectionMultiplexer.Connect(redisConnenctionString); } catch (Exception) { //throw new Exception('Redis服务未启用,请开启该服务,并且请注意端口号,本项目使用的的6319,而且我的是没有设置密码。'); } } return this.redisConnection; } /// <summary> /// 清除 /// </summary> public void Clear() { foreach (var endPoint in this.GetRedisConnection().GetEndPoints()) { var server = this.GetRedisConnection().GetServer(endPoint); foreach (var key in server.Keys()) { redisConnection.GetDatabase().KeyDelete(key); } } } /// <summary> /// 判断是否存在 /// </summary> /// <param name='key'></param> /// <returns></returns> public bool Get(string key) { return redisConnection.GetDatabase().KeyExists(key); } /// <summary> /// 查询 /// </summary> /// <param name='key'></param> /// <returns></returns> public string GetValue(string key) { return redisConnection.GetDatabase().StringGet(key); } /// <summary> /// 获取 /// </summary> /// <typeparam name='TEntity'></typeparam> /// <param name='key'></param> /// <returns></returns> public TEntity Get<TEntity>(string key) { var value = redisConnection.GetDatabase().StringGet(key); if (value.HasValue) { //需要用的反序列化,将Redis存储的Byte[],进行反序列化 return SerializeHelper.Deserialize<TEntity>(value); } else { return default(TEntity); } } /// <summary> /// 移除 /// </summary> /// <param name='key'></param> public void Remove(string key) { redisConnection.GetDatabase().KeyDelete(key); } /// <summary> /// 设置 /// </summary> /// <param name='key'></param> /// <param name='value'></param> /// <param name='cacheTime'></param> public void Set(string key, object value, TimeSpan cacheTime) { if (value != null) { //序列化,将object值生成RedisValue redisConnection.GetDatabase().StringSet(key, SerializeHelper.Serialize(value), cacheTime); } } /// <summary> /// 增加/修改 /// </summary> /// <param name='key'></param> /// <param name='value'></param> /// <returns></returns> public bool SetValue(string key, byte[] value) { return redisConnection.GetDatabase().StringSet(key, value, TimeSpan.FromSeconds(120)); } |
|