1.简介设计模式
上图中在点击菜单按钮后不断的弹出子窗体,显然这种方式是不合理的。此场景正是能够运用单例模式来解决的一种运用。缓存
其核心本质就是让类的对象只有一个,使用到的地方还包括:线程池、缓存、对话框等。多线程
单例模式其实很好理解,最核心的含义就是经过该设计模式来确保一个类只有一个实例,并对外提供一个全局访问的方式。并发
该模式最关键点就是“确保”,如何真正的确保一个类只有一个实例,理解单例的过程就是如何懂得实现真正意义上的“确保”。ide
2.单例模式的“三板斧”函数
2.1拒绝对外性能
设想下若是阿拉神灯容许你能够许一个愿望成真,这样的机会你会给别人吗?此场景一样能够隐喻到咱们当下讲述的单例模式,spa
单例模式目前需求是一个类只能实例化一个对象,那么这样的机会你会给外人吗?此场景引入了实现单例模式的第一点——拒绝对外,线程
这里的拒绝对外说的是类建立对象的构造函数不容许外部进行调用。设计
如何在代码上实现呢?单例模式的一个关键点之一,构造函数私有化,这也印证了上述中说到的若是有件事情你只能作一次那么你确定会留给本身。
public Singleton
{
private Singleton (){}
}
2.2.惟一对象的容器
经过私有构造函数对外关闭了实例化对象的入口,此时类的内部是实例化的惟一途径。那么如今须要为实例对象准备一个存储的容器来存储——声明静态变量。
public Singleton
{
private Singleton (){ }
private static Singleton uniqueObj=null;
}
2.3建立惟一对象并提供全局访问点
目前已经实现了构造函数私有化和对象的容器,那么如今须要实例化一个惟一的对象并提供外部访问的一种方式。
外部目前已经没法获取实例,那么惟一的方式就是提供一个静态的函数,外部直接经过类名来调用函数。在函数内部判断容器变量是否为空,
为空表示尚未建立实例,那么就经过私有构造函数来建立实例赋值到容器变量。在不为空的状况下说明惟一的对象已经存在,直接返回容器变量。
单例模式的“三板斧”最终代码以下:
如此一来就能够实现一个普通的单例模式,这里为何说普通由于这个“三板斧”的组成是一个最常规而且还存在一些瑕疵的单例模式,
后续会讲具体的缘由,咱们先验证下这样的方式是否能保证类仅有一个实例。如图:
3.单例模式“三板斧”的瑕疵——多线程并发
上述讲的普通的单例模式若是使用了多线程获取对象实例,那么会致使调用方法后建立多个实例从而不能保证单个实例的需求。
咱们以构造函数的特色来验证下这种状况,如图:
上图的验证中构造函数被执行屡次,那么表明建立多个实例,从而证实多线程并发的时候没法保证对象的实例仅有一个。
4.如何解决多线程并发带来的问题
4.1.线程同步
使用C#关键字lock的特性实现线程同步。在并发的时候可能会有多个线程同时进入实例化对象的代码块中,
利用线程同步在访问实例化对象代码块时线程造成排队机制来确保不会同时建立多个实例。改进下代码来看看效果
如图:
此方式会有一个问题,实际上在第一次执行此方法时才须要同步。由于一旦设置好了实例对象,就没有必要进行同步。
而目前的作法是每次使用的时候都会执行同步,而同步次数过多会下降程序的性能,若是不考虑性能的消耗,那么也能够选择此方式。
4.2.静态变量
直接使用声明一个静态字段并对其进行实例化赋值,此作法是利用静态的原理来作到类仅有一个对象的方式。
咱们能够回顾分析下静态成员的特色:
GC不会对静态成员进行资源回收而且会常驻于程序内存中;
静态成员使用静态构造函数初始化,静态构造函数由CLR执行而且只会执行一次从而保证了静态变量只会存储一个实例,另外该方式也解决了多线程并发带来的问题。
代码以下:
1 class Singleton 2 { 3 private Singleton() { } 4 private static Singleton uniqueObj = new Singleton(); 5 public static Singleton CreateInstance() 6 { 7 return uniqueObj; 8 } 9 }
4.2.1.“延迟实例化”
该方式是一种快速实现方式,代码上甚至能够简写:直接将静态变量改为公有的,外部直接访问这个公有的静态变量来实现单例模式。
该模式一样存在一个问题:此模式摒弃了“延迟实例化”。在使用lock的方式中,咱们获取实例的方式是经过一个静态函数来获取的,
而目前的方式是直接使用静态字段。熟悉静态成员初始化的朋友应该清楚,静态成员初始化赋值的时候是当有代码对类型第一次访问的时候进行的。
咱们试想一下,若是程序中存在一个单例模式,可是在使用某个功能时没有涉及到单例模式的使用,可是使用的这个功能访问到了单例模式的类型。
这就致使单例模式的对象没有使用的需求却被建立,这就致使了没必要要的消耗。而lock方式中经过静态函数来获取,那么此方式只要真正有道单例模式的时候才会建立对应的实例对象。
此方式也有个名词叫作饿汉式,比如一桌菜没有上齐,饥饿的大汉就能够吃起来。
4.3.“双重检查加锁”
此方式是目前最为完善的方式,经过“双重检查加锁”的方式能够保证方法执行时只会在第一次的时候进行同步避免了屡次同步的性能消耗,同时也能实现“延迟实例化”。
PS:另外经过程序进行线程调试就能够看到该方式只在第一次方法时才进行同步。
代码以下:
1 class Singleton 2 { 3 private Singleton() { Console.WriteLine("每建立一个对象调用一次构造函数"); } 4 private static Singleton uniqueObj = null; 5 private static object obj = new object(); 6 7 public static Singleton CreateInstance() 8 { 9 if (uniqueObj == null) 10 { 11 lock (obj) 12 { 13 if (uniqueObj == null) 14 { 15 uniqueObj = new Singleton(); 16 } 17 } 18 } 19 return uniqueObj; 20 } 21 }
5.Demo源码
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Data; 5 using System.Data.OleDb; 6 using System.Diagnostics; 7 using System.IO; 8 using System.Linq; 9 using System.Text; 10 using System.Text.RegularExpressions; 11 using System.Threading; 12 13 namespace MyDebug 14 { 15 16 class Singleton 17 { 18 #region MyRegion 19 private Singleton() { Console.WriteLine("每建立一个对象调用一次构造函数"); } 20 private static Singleton uniqueObj = null; 21 private static object obj = new object(); 22 23 public static Singleton CreateInstance() 24 { 25 if (uniqueObj == null) 26 { 27 lock (obj) 28 { 29 if (uniqueObj == null) 30 { 31 uniqueObj = new Singleton(); 32 } 33 } 34 } 35 return uniqueObj; 36 } 37 #endregion 38 39 #region 静态变量实例化(已注释) 40 //private Singleton() { } 41 //private static Singleton uniqueObj = new Singleton(); 42 //public static Singleton CreateInstance() 43 //{ 44 // return uniqueObj; 45 //} 46 #endregion 47 48 #region lock同步(已注释) 49 //private static Singleton uniqueObj = null; 50 //private static object obj = new object(); //lock资源对象 51 //public static Singleton CreateInstance() 52 //{ 53 // lock (obj) 54 // { 55 // if (uniqueObj == null) 56 // { 57 // uniqueObj = new Singleton(); 58 // } 59 // } 60 // return uniqueObj; 61 //} 62 #endregion 63 } 64 65 class Program 66 { 67 static void Main(string[] args) 68 { 69 //多线程 70 for (int i = 0; i < 5; i++) 71 { 72 Thread r1 = new Thread(() => 73 { 74 Singleton B = Singleton.CreateInstance(); 75 }); 76 r1.Start(); 77 } 78 79 Console.ReadKey(); 80 } 81 82 83 84 85 } 86 87 88 89 90 }