为什么将单例模式排名第一,很简单,面试的时候聊到设计模式,大概率就从单例模式开始入手,循循渐进。
当然,我们学习单例模式不是为了去应付面试,而是学好设计模式后,融汇贯通,应用于我们的设计,开发,项目中。
单例模式是最简单的设计模式之一
单例模式【Singleton Pattern】:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
两层含义:1,有且仅有一个实例
2,有一个访问他的全局访问点
我们来想象一个场景,放眼全球,所有的党派,都只有一个主席(这应该没有例外吧)这个案例,如果要拜访他,是不是需要一个全局的访问点。
对于一个类来说,我们怎样保证他仅有一个实例,初步想到的是不准外部实例化,因为如果可以外部实例化,我就可以实例化无数个。那不准外部实例化,就只能内部了,那就动手修改内部的构造方法。
(敲黑板,划重点) 所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效
如此,我们只需要显示的修改构造方法即可,既然不准外部调用,那我们用private修饰构造方法即可
第一步,控制外部实例化我们就做到了
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
/// <summary> /// 党派 /// </summary> public class Party { /// <summary> /// 私有化构造函数,不准外部实例化 /// </summary> private Party() { } } }
那我们需要一个主席,一个党派总不能群龙无首吧,于是我们定义一个主席,并且我们要提供一个访问的点吧,不然别人想和主席聊两句都找不到人
第二步,创建一个主席,并提供一个访问点
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
/// <summary> /// 党派 /// </summary> public class Party { /// <summary> /// 我是党派主席 /// </summary> private static Party _chairman; /// <summary> /// 私有化构造函数,不准外部实例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { if (_chairman == null)//主席不存在则创建一个主席 { _chairman = new Party(); } return _chairman; } } }
哇哇哇~!!!私有构造函数,为什么这里可以调用啊,不是不让调用么!!!这是同一个类中,同一个类中,私有,公有,保护,都可以随意调用。
我们再让主席讲一句话
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
/// <summary> /// 党派 /// </summary> public class Party { /// <summary> /// 我是党派主席 /// </summary> private static Party _chairman; /// <summary> /// 私有化构造函数,不准外部实例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { if (_chairman == null)//主席不存在则创建一个主席 { _chairman = new Party(); } return _chairman; } public void Say() { Console.WriteLine("同志们好!"); } }
这样我们的一个简单的单单例模式就算完成了,我们再看看如何调用
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
class Program { static void Main(string[] args) { Party chairman = Party.GetChairman(); chairman.Say(); } }
那么问题来了,单线程操作的时候,以上没有问题,那多线程的时候呢,多个人同时访问主席,会不会可能产生多个主席呢,多个主席,是不是就违反了单例模式最基本的原则,仅有一个实例呢。多个主席,难道打一架,胜者为王么?
于是我们需要再次优化我们的代码
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
/// <summary> /// 党派 /// </summary> public class Party { /// <summary> /// 我是党派主席 /// </summary> private static Party _chairman; /// <summary> /// 静态只读的进程辅助对象 /// </summary> private static readonly object syncRoot = new object(); /// <summary> /// 私有化构造函数,不准外部实例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { lock (syncRoot)//锁你丫的 { if (_chairman == null)//主席不存在则创建一个主席 { _chairman = new Party(); } } return _chairman; } public void Say() { Console.WriteLine("同志们好!"); } } }
以上,再同一个时刻加了锁的那部分代码,只有一个线程可以进入。
如果lock不懂,我知道你们懒
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放
那么问题又来了,为什么每一次都要锁,为什么,见一次主席那么那么难么?能不能没有创建的时候我才锁,有主席的时候我直接访问啊
当然可以,这就是双重锁定
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
/// <summary> /// 党派 /// </summary> public class Party { /// <summary> /// 我是党派主席 /// </summary> private static Party _chairman; /// <summary> /// 静态只读的进程辅助对象 /// </summary> private static readonly object syncRoot = new object(); /// <summary> /// 私有化构造函数,不准外部实例化 /// </summary> private Party() { } /// <summary> /// 找主席 /// </summary> /// <returns></returns> public static Party GetChairman() { if (_chairman == null) //主席不存在,我再锁定,进行选举 { lock (syncRoot) //锁你丫的 { if (_chairman == null) //主席不存在则创建一个主席 { _chairman = new Party(); } } } return _chairman; } public void Say() { Console.WriteLine("同志们好!"); } } }
这里我们就先判断了主席是否存在,不存在,我们再闭门选举,等我们选举出来了,你再来访问。
我们既优化了执行,也保证了仅有一个主席,保证了多线程访问的安全性。
其实还有什么饿汉式,懒汉式的区别
从字面理解
饿汉,一开始我就实例化自己
/// <summary> /// 我是党派主席 /// </summary> private static Party _chairman = new Party();
懒汉,被引用时候,才实例化自己
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
/// <summary> /// 我是党派主席 /// </summary> private static Party _chairman ;
具体使用哪一种,应该根据实际情况来定,无优劣之分,值得注意的是线程安全
总结下
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
1、要求生产唯一序列号。
2、计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
以上就是关于单例模式的分享
一路前行,风雨无阻