本笔记摘抄自:http://www.javashuo.com/article/p-vvpmrjym-bg.html,记录一下学习过程以备后续查用。html
1、引言设计模式
设计模式的分类:多线程
1)依目的:ide
建立型(Creational)模式:负责对象建立函数
结构型(Structural)模式:处理类与对象间的组合性能
行为型(Behavioral)模式:类与对象交互中的职责分配学习
2)依范围:优化
类模式:处理类与子类的静态关系spa
对象模式:处理对象间的动态关系线程
注:本系列文章依目的分类来进行。
2、单例模式的介绍
单例模式:英文名称--Singleton Pattern;分类--建立型;定义--一个类仅有一个实例。
2.一、动机(Motivate)
在软件系统中,常常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?这应该是类设计者的责任,而不是使用者的责任。
2.二、意图(Intent)
保证一个类仅有一个实例,并提供一个该实例的全局访问点。--《设计模式GoF》
2.三、结构图(Structure)
2.四、模式组成
这个模式里面只有一个类型,就是Singleton类型,而且这个类只有一个实例,能够经过Instance()方法获取该类型的实例。
2.五、代码实现
既然是单实例,确定会涉及到多线程的问题。
2.5.1单线程Singleton模式的实现
class Program { /// <summary> /// 单例模式的实现 /// </summary> public sealed class Singleton { //定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; //定义私有构造函数,使外界不能建立该类实例。 private Singleton() { Console.WriteLine("Singleton对象已被建立。"); } /// <summary> /// 定义公有方法提供一个全局访问点,也能够定义公有属性来提供全局访问点。 /// </summary> /// <returns></returns> public static Singleton GetInstance() { //若是类的实例不存在则建立,不然直接返回。 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } } static void Main(string[] args) { #region (1)单例模式 var singleton = Singleton.GetInstance(); Console.Read(); #endregion } }
运行结果以下:
私有的实例构造器是屏蔽外界的调用,上面的单例模式的实如今单线程下确实是完美的,也很好的知足了咱们单线程环境的需求。
在多线程环境下,使用Singleton模式仍然有可能获得Singleton类的多个实例对象。由于在两个线程同时运行GetInstance方法时,
此时两个线程判断(uniqueInstance==null)这个条件时都返回真,此时两个线程就都会建立Singleton的实例。
2.5.2多线程Singleton模式的实现
class Program { /// <summary> /// 单例模式的实现 /// </summary> public sealed class Singleton { //定义一个静态变量来保存类的实例 private static volatile Singleton uniqueInstance; //定义一个标识确保线程同步 private static readonly object locker = new object(); //定义私有构造函数,使外界不能建立该类实例。 private Singleton() { Console.WriteLine("Singleton对象已被建立。"); } /// <summary> /// 定义公有方法提供一个全局访问点,也能够定义公有属性来提供全局访问点。 /// </summary> /// <returns></returns> public static Singleton GetInstance() { //当第一个线程运行到这里时,此时会对locker对象"加锁"。 //当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁。 //lock语句运行完以后(即线程运行完以后)会对该对象"解锁"。 lock (locker) { // 若是类的实例不存在则建立,不然直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } return uniqueInstance; } } static void Main(string[] args) { #region (1)单例模式 for (int i = 1; i <= 10; i++) { Thread thread = new Thread(new ParameterizedThreadStart(Worker)); thread.Start(i); } Console.Read(); #endregion } private static void Worker(object parameter) { Console.WriteLine($"Thread {parameter} is running."); Singleton.GetInstance(); } }
运行结果以下:
上面的解决方案确实能够解决多线程的问题,可是上面代码每一个线程都会对线程辅助对象locker加锁以后再判断实例是否存在,这个是彻底没有必要的。
由于当第一个线程建立了该类的实例以后,后面的线程此时只须要直接判断(uniqueInstance==null)为假便可,从而减小额外的开销以提升性能。
为了改进上面实现方式的缺陷,咱们只须要在lock语句前面加一句(uniqueInstance==null)的判断,这种双层if加lock的实现方式,咱们称它为
“双重锁定(Double Check)”。
class Program { /// <summary> /// 单例模式的实现 /// </summary> public sealed class Singleton { //定义一个静态变量来保存类的实例 private static volatile Singleton uniqueInstance; //定义一个标识确保线程同步 private static readonly object locker = new object(); //定义私有构造函数,使外界不能建立该类实例。 private Singleton() { Console.WriteLine("Singleton对象已被建立。"); } /// <summary> /// 定义公有方法提供一个全局访问点,也能够定义公有属性来提供全局访问点。 /// </summary> /// <returns></returns> public static Singleton GetInstance() { //当第一个线程运行到这里时,此时会对locker对象"加锁"。 //当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁。 //lock语句运行完以后(即线程运行完以后)会对该对象"解锁"。 //双重锁定只须要一句判断就能够了 if (uniqueInstance == null) { lock (locker) { // 若是类的实例不存在则建立,不然直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } } static void Main(string[] args) { #region (1)单例模式 for (int i = 1; i <= 10; i++) { Thread thread = new Thread(new ParameterizedThreadStart(Worker)); thread.Start(i); } Console.Read(); #endregion } private static void Worker(object parameter) { Console.WriteLine($"Thread {parameter} is running."); Singleton.GetInstance(); } }
volatile修饰:编译器在编译代码的时候会对代码的顺序进行微调,用volatile修饰保证了严格意义的顺序。一个定义为volatile的变量是说这变量可能会
被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都当心地从新读取这个变量的值,而
不是使用保存在寄存器里的备份。
3、C#中实现了单例模式的类
如今咱们看看,如何使用C#语言的特性来实现单例的Singleton模式。
//Singleton模式的实现 public sealed class Singleton { public static readonly Singleton instance = new Singleton(); private Singleton() { } } //以上是内联初始化(生成的同时进行初始化)的单例模式,它等同于: public sealed class Singleton { public static readonly Singleton instance; //静态构造函数,CLR只执行一次。 static Singleton() { instance = new Singleton(); } //私有构造函数,防止外界调用 private Singleton() { } }
内联初始化实际上是把静态的字段放到静态构造器去初始化。只要想访问静态字段,一定已经在使用以前先执行静态构造器,这样可以精确地保证使用
的时候必定能拿到实例,若是不使用也不会实例化对象,这也就是延时加载的功能。它一样可以支持多线程环境,由于只可能有一个线程执行静态构造
器,不存在多个线程去执行静态构造器(感受就是程序已经自动为咱们加锁了)。
它的一点弊端就是:静态构造器只能声明为一个私有的、无参数的构造器,于是不支持参数化的实例化方法。
须要说明的是:HttpContext.Current就是一个单例,它们是经过Singleton的扩展方式来实现的。
4、Singleton模式的扩展
1)将一个实例扩展到n个实例,例如对象池的实现。(n不是指无限个实例,而是固定的某个数。)
2)将new构造器的调用转移到其余类中,例如多个类协同工做环境中,某个局部环境只须要拥有某个类的一个实例。
3)理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的实例构造器的任意调用”。
5、单例模式的实现要点
1)Singleton模式是限制而不是改进类的建立。
2)Singleton类中的实例构造器能够设置为Protected以容许子类派生。
3)Singleton模式通常不要支持Icloneable接口,由于这可能致使多个对象实例,与Singleton模式的初衷违背。
4)Singleton模式通常不要支持序列化,这也有可能致使多个对象实例,这也与Singleton模式的初衷违背。
5)Singleton只考虑对象建立的管理,没有考虑销毁的管理。为何这样作呢?由于Net平台是支持垃圾回收的,因此咱们通常没有必要对其进行销毁
处理。
6)理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的构造器的任意调用”。
7)能够很简单的修改一个Singleton,使它有少数几个实例,这样作是容许的并且是有意义的。
5.1单例模式的优势
1)实例控制:Singleton会阻止其余对象实例化其本身的Singleton对象的副本,从而确保全部对象都访问惟一实例。
2)灵活性:由于类控制了实例化过程,因此类能够更加灵活修改实例化过程。
5.2单例模式的缺点
1)开销:虽然数量不多,但若是每次对象请求引用时都要检查是否存在类的实例,将仍然须要一些开销,能够经过使用静态初始化解决此问题。
2)可能的开发混淆:使用Singleton对象(尤为在类库中定义的对象)时,开发人员必须记住本身不能使用new关键字实例化对象。由于可能无
法访问库源代码,所以应用程序开发人员可能会意外发现本身没法直接实例化此类。
3)对象的生存期:Singleton不能解决删除单个对象的问题。由于它包含对该静态的私有字段的引用,静态字段是不能被CLR回收内存的,该实
例会和应用程序生命周期同样长,一直存在。
5.3单例模式的使用场合
1)当类只能有一个实例并且客户能够从一个众所周知的访问点访问它时。
2)当这个惟一实例应该是经过子类化可扩展的,而且客户应该无需更改代码就能使用一个扩展的实例时。