定义
百度文库:单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
标准定义:Singleton保证一个类仅有一个实例,并提供一个访问它的全局访问点。
UML图
示例
(1.1)定义一个私有的静态的全局变量
public class Singleton { //定义一个私有的静态的全局变量 private static Singleton singleton; }
(1.2)设置私有构造函数
public class Singleton { //定义一个私有的静态的全局变量 private static Singleton singleton; /// <summary> /// 设置私有构造函数(外部无法使用new创建实例) /// </summary> private Singleton() { } }
(1.3)设置一个全局访问点,静态方法供外部调用
public class Singleton { //定义一个私有的静态的全局变量 private static Singleton singleton; /// <summary> /// 设置私有构造函数(外部无法使用new创建实例) /// </summary> private Singleton() { } /// <summary> /// 设置一个全局访问点,静态方法供外部调用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { //判断保证实例化一次 if (singleton == null) singleton = new Singleton(); return singleton; } }
(1.4)调用代码
class Program { static void Main(string[] args) { Singleton instanceA = Singleton.GetInstance(); Singleton instanceB = Singleton.GetInstance(); Console.WriteLine(instanceA.Equals(instanceB) ? "一个实例" : "不是一个实例"); Console.ReadKey(); } }
结果显示一个实例,那么一个初步的单例模式我们就体验完毕了
优化与思考
刚刚我们属于单线程,那有没有这样一种情况,有多个线程一起创建当前实例,一个线程刚刚进入判断IF中,另一个也进来了,此时兴许有实例化2次的情况,这样在某种情况下虽然不会报错,但是默认多次实例化却偏离了我们单例的初衷,下面我们来大概模拟一下这种情况.
(2.1)修改创建-我们在实例化代码位置随意输出内容判断进入次数
public class Singleton { //定义一个私有的静态的全局变量 private static Singleton singleton; /// <summary> /// 设置私有构造函数(外部无法使用new创建实例) /// </summary> private Singleton() { } /// <summary> /// 设置一个全局访问点,静态方法供外部调用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { //判断保证实例化一次 if (singleton == null) { singleton = new Singleton(); Console.WriteLine(DateTime.Now.ToString()); } return singleton; } }
这里我输出了时间
(2.2)调用多线程这里我们用2个线程举例
class Program { static void Main(string[] args) { Singleton instance = null; Parallel.For(0, 2, (i) => { instance = Singleton.GetInstance(); }); Console.ReadKey(); } }
(2.3)结果居然出现了2次,也就是实例化2次
分析
为了避免这种情况,我们可以将线程锁定。意味着锁定之后每次只会有一个线程进入判断,实例化对象。一定程度避免了多次实例化。
(3.1)增加只读静态对象("锁"对象)
//只读静态对象 private static readonly object syncObject = new object();
(3.2)实例化加入判断
/// <summary> /// 设置一个全局访问点,静态方法供外部调用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { if (singleton == null) { lock (syncObject) { //判断保证实例化一次 singleton = new Singleton(); Console.WriteLine(DateTime.Now.ToString()); } } return singleton; }
(3.3)结果
???怎么回事,怎么又出现2次了。。#¥¥&&@@@)¥)(@*@
所以最终我们双重锁定来解决问题在lock内部再加一层判定
/// <summary> /// 设置一个全局访问点,静态方法供外部调用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { if (singleton == null) { lock (syncObject) { //判断保证实例化一次 if (singleton == null) { singleton = new Singleton(); Console.WriteLine(DateTime.Now.ToString()); } } } return singleton; }
结果:
补充:
我们上边的例子采用的都是lazy load也就是懒加载,用的时候加载,当然还有一种方式就是饿汉模式,就是类初始化的时候直接实例化实例
/// <summary> /// 饿汉 /// </summary> public sealed class SingletonHungry { private static readonly SingletonHungry singleton = new SingletonHungry(); public static SingletonHungry GetInstance() { return singleton; } }
优点
(1)实例控制:单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
(2)灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点
(1)开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
(2)可能的开发混淆:使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
(3)对象生存期:不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。