一、引言
单例模式应该算是23种设计模式中比较简单的,它属于创建型的设计模式,关注对象的创建。
二、概念
单例模式是23个“Gang Of Four”的设计模式之一,它描述了如何解决重复出现的设计问题,以设计灵活且可复用的面向对象软件,使对象的实现、更改、测试和重用更方便。
单例模式解决了以下问题:
如何确保类只有一个实例?
如何控制类的实例化?
如何限制类的实例数量?
单例模式是如何解决以上问题的呢?
这个设计模式的关键点在于使类控制其自身的实例化。
隐藏类的构造函数(定义私有构造函数)来确保类不能从外部实例化。
使用静态函数轻松访问类实例(Singleton.getInstance())。
三、实现
1、懒汉式单例
1 using System.Threading; 2 3 public class SingletonTest 4 { 5 private static SingletonTest instance = null; 6 7 /// <summary> 8 /// 隐藏类的构造函数(定义私有构造函数)来确保类不能从外部实例化 9 /// </summary>10 private SingletonTest()11 {12 Console.WriteLine("******单例类被实例化******");13 }14 15 /// <summary>16 /// 使用静态函数轻松访问类实例17 /// </summary>18 /// <returns><see cref="SingletonTest"/></returns>19 public static SingletonTest GetInstance()20 {21 if (instance == null)22 {23 instance = new SingletonTest();24 }25 26 return instance;27 }28 29 public void PrintSomething()30 {31 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");32 Console.WriteLine("Singleton Pattern Test");33 }34 }
上述代码在单线程的情况下是能正常运行的,符合单例模式的定义。
但是,多线程的情况呢?
我们使用以下代码进行测试
1 // 多线程情况 2 for (int i = 0; i < 10; i++) 3 { 4 Task.Run( 5 () => 6 { 7 SingletonTest singleton1 = SingletonTest.GetInstance(); 8 singleton1.PrintSomething(); 9 });10 }
结果如下
不出所料,类SingletonTest被实例化了多次,不符合单例模式的要求,那如何解决这个问题呢?
既然是多线程引起的问题,那就要使用线程同步。我们在这里使用锁来实现。
1 using System; 2 using System.Threading; 3 4 public class SingletonTest 5 { 6 private static SingletonTest instance = null; 7 8 private static object lockObject = new object(); 9 10 /// <summary>11 /// 隐藏类的构造函数(定义私有构造函数)来确保类不能从外部实例化12 /// </summary>13 private SingletonTest()14 {15 Thread.Sleep(500);16 Console.WriteLine("******单例类被实例化******");17 }18 19 /// <summary>20 /// 使用静态函数轻松访问类实例21 /// </summary>22 /// <returns><see cref="SingletonTest"/></returns>23 public static SingletonTest GetInstance()24 {25 // 双重检查锁定的方式实现单例模式26 if (instance == null)27 {28 lock (lockObject)29 {30 if (instance == null)31 {32 instance = new SingletonTest();33 }34 }35 }36 37 return instance;38 }39 40 public void PrintSomething()41 {42 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");43 Console.WriteLine("Singleton Pattern Test");44 }45 }
上述代码在创建实例前,检查了两次实例是否为空,第一次判空是否有必要呢(上述代码26行)?为什么呢?我们想象这样的一种场景,在已经初始化类实例的情况下,是否还需要获取锁?答案是不需要,所以加第一个判断条件(上述代码26行)。
再次运行测试代码,结果如下:
从结果来看,类只被实例化了一次,解决了多线程下类被多次实例化的问题。但是指令重排序是否对此有影响?是否需要volatile关键字?欢迎大佬来解答一下。
2、饿汉式单例(推荐)
第一种方式有点繁琐,可以简单点吗?如下
1 using System; 2 using System.Threading; 3 4 public class SingletonTest2 5 { 6 // 静态变量的方式实现单例模式 7 private static readonly SingletonTest2 Instance = new SingletonTest2(); 8 9 private SingletonTest2()10 {11 Thread.Sleep(1000);12 Console.WriteLine("******单例类被实例化******");13 }14 15 public static SingletonTest2 GetInstance()16 {17 return Instance;18 }19 20 public void PrintSomething()21 {22 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");23 Console.WriteLine("Singleton Pattern Test");24 }25 }
这种实现方式利用的是.NET中静态关键字static的特性,使单例类在使用前被实例化,并且只实例化一次,这个由.NET框架保证。
这种方式存在问题,在没有使用到类中的成员时候就创建实例了,能否在使用到类成员的时候才创建实例呢?如下图
1 using System; 2 using System.Threading; 3 4 public class SingletonTest2 5 { 6 // 静态变量的方式实现单例模式 7 private static readonly Lazy<SingletonTest2> Instance = new Lazy<SingletonTest2>(() => new SingletonTest2()); 8 9 private SingletonTest2()10 {11 Thread.Sleep(1000);12 Console.WriteLine("******初始化单例模式实例*****");13 }14 15 public static SingletonTest2 GetInstance()16 {17 return Instance.Value;18 }19 20 public void PrintSomething()21 {22 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");23 Console.WriteLine("Singleton Pattern Test");24 }25 }
我们使用了Lazy关键字来延迟实例化。
四、例外
值得注意的是,反射会破坏单例模式,如下代码,能直接调用类的私有构造函数,再次实例化。
1 // 反射破坏单例2 var singletonInstance = System.Activator.CreateInstance(typeof(SingletonTest2), true);
怎么避免呢?类的实例化都需要调用构造函数,那么我们在构造函数中加入判断标识即可。尝试实例化第二次的时候,就会抛异常。
1 private static bool isInstantiated; 2 3 private SingletonTest2() 4 { 5 if (isInstantiated) 6 { 7 throw new Exception("已经被实例化了,不能再次实例化"); 8 } 9 10 isInstantiated = true;11 Thread.Sleep(1000);12 Console.WriteLine("******单例类被实例化******");13 }
五、应用
那么实际应用中,哪些地方应该用单例模式呢?在这个类只应该存在一个对象的情况下使用。哪些地方用到了单例模式呢?
六、总结
俗话说,凡事都有两面性。单例模式确保了类只有一个实例,也引入了其他问题:
代码下载:https://github.com/hzhhhbb/SingletonPattern
七、参考资料
原文链接:http://www.cnblogs.com/hzhhhbb/p/11373553.html
本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728