(一)单例设计描述
只要了解过设计模式的同学都会知道:单例设计模式,大家都知道单例设计模式是一种创建行的设计模式。既然是创建型,那么先来讲讲,对象的创建的过程吧。
--静态成员:静态成员在程序加载的时候,就会加载进内存。
--实例成员:只有new的时候才有实例成员。1、为实例的数据字段分配内存,然后初始化对象的附加字段(类型指针和同步索引块),最后调用类型的实例构造器来设置对象的初始化状态。
单例模式:一般用在一个类的创建对象很消耗资源,消耗时间,并且系统要保证只有一个对象的时候。一句话,对象的创建并不是很耗时间,不要刻意去套用单例模式,单例模式必须是在单例的时候,才单例。
(二) 单例模式的演变
下面我们来模拟实际情况
public class Singleton { /// <summary> /// 对象会持有资源 /// </summary> private List<string> _connList=new List<string>() { "测试数据库连接","测试数据库连接2","测试数据库连接3","测试数据库连接4" }; public Singleton() { long lResult = 0; for (int i = 0; i < 100000; i++){ lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被构造一次",this.GetType().Name); } public void Show() { Console.WriteLine("调用了Show"); } }
这个类的创建需要耗费很多资源,里面有个Show方法。
那么接下来,实际中,我们可能有十个地方要用到这个类里面的Show方法,我们的做法是这样的
//那么接下来,我们这里要调用十次Show方法 for (var i = 0; i < 10; i++) { var singletonObj = new Singleton(); singletonObj.Show(); }
这里每次调用一次,都需要耗费很多资源和时间,这里可能有些同学就会说,那我把这个singletonObj=new Singleton()提取出来,放到最外面来。
那行,按照我们需要,我们把var singletonObj=new Singletone()放到外面,如下所示
貌似这样就解决了我们的问题,但是各位你们想一想,我们一个系统是有多个人开发的,A这里这样做,B可能不知道这里有这个声明,那他可能就还是一样去New Singleton,还是导致我们系统中,存在大量这个对象。
我们应该要如何解决这个问题呢? 如何保证这个对象在整个系统只被创建一次呢?
单例模式--单线程
从上面的问题,我们也可以看出因为谁都可以在New Singleton,所以导致了这个问题。那按照这个想法,那我们就想啦,那就把构造函数私有化呗,私有化完了之后,我们应该还要提供一个方法或者啥的,给外面调用(也只能是静态的成员),构造函数私有化了,外面是不可以New了的
那就按照,刚刚的说法,我们来进行一次改进
public class Singleton { /// <summary> /// 对象会持有资源 /// </summary> private List<string> _connList=new List<string>() { "测试数据库连接","测试数据库连接2","测试数据库连接3","测试数据库连接4" }; private Singleton(){ long lResult = 0; for (int i = 0; i < 100000; i++){ lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被构造一次",this.GetType().Name); } public static Singleton CreateInstance(){ return new Singleton(); } public void Show() { Console.WriteLine("调用了Show"); } }
按照我们上面这个写法,把构造函数私有化了,然后在静态方法里面New Singletone();
调用结果如下:
for (var i = 0; i < 10; i++) { var singletonObj = Singleton.CreateInstance(); singletonObj.Show(); } //写进里面去了,是为了模拟有十个不同的开发,再调用
那结果,还是没有达到我们想要的,那现在问题就是,对象没有重用,因为我们每次new,导致了对象没有做到重用,那就让对象进行重用呗。最简单的方法,就是给一个静态的字段(为啥静态呢,因为我们那边方法是静态的),然后做一个判断,如果对象为空,那么我们就创建,如果不为空,就不用创建了,如下所示。
我们在原来的基础上,做了如上图的改进。结果如下,
我们想要的结果实现了,多次调用的时候,做了重用对象,只构造了一次。本来想着单例模式就这样结束了,编程世界里面,我们总是要考虑一下,多线程的情况(刚刚我们的情况是单线程的)
单例模式--多线程
这个是用Task.Run()开启了也给多线程的异步调用
for (var i = 0; i < 10; i++){ Task.Run(()=>{ var singletonObj = Singleton.CreateInstance(); singletonObj.Show(); }); } Thread.Sleep(5000);
通过上面的结果,我们可以看出,我们的之前的做法,在多线程环境还是会调用多次。
有些同学就会说用lock锁啦,行,我们就给他加上一把锁。
public class Singleton { /// <summary> /// 对象会持有资源 /// </summary> private List<string> _connList=new List<string>() { "测试数据库连接","测试数据库连接2","测试数据库连接3","测试数据库连接4" }; private static Singleton singletonObj = null; private static readonly object singleTonObjLock = new object(); //加锁,之后这里为啥要用readonly,大家可以找 private Singleton(){ long lResult = 0; for (int i = 0; i < 100000; i++){ lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被构造一次",this.GetType().Name); Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}调用一次"); } public static Singleton CreateInstance() { lock (singleTonObjLock) //很多同学都说用lock this,this肯定是不行的,因为lock是lock引用的,如果这个this的引用改变了... { if (singletonObj == null) { singletonObj = new Singleton(); } } return singletonObj; } public void Show() { Console.WriteLine("调用了Show"); } }
看我们给他加完锁的时候效果。
嗯,实现了我们想要的效果了,说明我们加锁是有效果的。到了这个时候,大家可能觉得一个单例模式应该就快结束了,那么我们再来看看这种情况。
单例模式--多线程(双if+lock)
通过上面的介绍,我们理解了单例模式的演变过程,也对单例模式,多线程有了更加深刻的印象。
(三)单例模式其他实现
就像我们一开始说的那样,单例模式,其实一个进程内,在多线程环境下,如何保证只有一个对象,这就是单例。也可以从这个定义看出,我们可以通过静态的构造函数来实现一个单例模式。
静态构造函数,是由CLR保证的,有且只会加载一次。
其他很多方法实现,都是利用static关键字的背后原因,在第一次使用类型之前被调用,且只会被调用一次。
(四)懒汉式,饿汉式
懒汉,就是说这个人很懒,需要用的时候,才构建。双if+lock这种就属于懒汉式。
饿汉:就是调用我这个类型,就会帮你创建好;管你用不用,我都会帮你创建;就是饿了吗,我后面介绍的利用static关键字的就是属于饿汉式;
(五)单例模式的使用场景
单例:必须单例才单例,反正没必要。单例模式实现都有性能,损失,静态方法。
单例:会把对象常驻内存,静态的。
单例的使用,多个人操作可能会对你影响,因为都是对同一份引用进行修改。
一般用在数据库连接,打印机,远程服务调用,等等这些大对象身上。
谢谢你阅读我的博客,如果有收获,请点一个赞(推荐)
原文地址:https://www.cnblogs.com/gdouzz/p/8319485.html