经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
设计模式-单例模式详解 - Vincent-Huang
来源:cnblogs  作者:Vincent-Huang  时间:2019/8/19 8:52:29  对本文有异议

一、引言

  单例模式应该算是23种设计模式中比较简单的,它属于创建型的设计模式,关注对象的创建。

二、概念

  单例模式是23个“Gang Of Four”的设计模式之一,它描述了如何解决重复出现的设计问题,以设计灵活且可复用的面向对象软件,使对象的实现、更改、测试和重用更方便。

单例模式解决了以下问题:

  • 如何确保类只有一个实例?

  • 如何轻松地访问类的唯一实例?
  • 如何控制类的实例化?

  • 如何限制类的实例数量?

单例模式是如何解决以上问题的呢?

  • 隐藏类的构造函数。
  • 定义一个返回类的唯一实例的公共静态操作。

这个设计模式的关键点在于使类控制其自身的实例化。

隐藏类的构造函数(定义私有构造函数)来确保类不能从外部实例化。

使用静态函数轻松访问类实例(Singleton.getInstance())。

三、实现

1、懒汉式单例

  1. 1 using System.Threading;
  2. 2
  3. 3 public class SingletonTest
  4. 4 {
  5. 5 private static SingletonTest instance = null;
  6. 6
  7. 7 /// <summary>
  8. 8 /// 隐藏类的构造函数(定义私有构造函数)来确保类不能从外部实例化
  9. 9 /// </summary>
  10. 10 private SingletonTest()
  11. 11 {
  12. 12 Console.WriteLine("******单例类被实例化******");
  13. 13 }
  14. 14
  15. 15 /// <summary>
  16. 16 /// 使用静态函数轻松访问类实例
  17. 17 /// </summary>
  18. 18 /// <returns><see cref="SingletonTest"/></returns>
  19. 19 public static SingletonTest GetInstance()
  20. 20 {
  21. 21 if (instance == null)
  22. 22 {
  23. 23 instance = new SingletonTest();
  24. 24 }
  25. 25
  26. 26 return instance;
  27. 27 }
  28. 28
  29. 29 public void PrintSomething()
  30. 30 {
  31. 31 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
  32. 32 Console.WriteLine("Singleton Pattern Test");
  33. 33 }
  34. 34 }

上述代码在单线程的情况下是能正常运行的,符合单例模式的定义。

但是,多线程的情况呢?

我们使用以下代码进行测试

  1. 1 // 多线程情况
  2. 2 for (int i = 0; i < 10; i++)
  3. 3 {
  4. 4 Task.Run(
  5. 5 () =>
  6. 6 {
  7. 7 SingletonTest singleton1 = SingletonTest.GetInstance();
  8. 8 singleton1.PrintSomething();
  9. 9 });
  10. 10 }

结果如下

不出所料,类SingletonTest被实例化了多次,不符合单例模式的要求,那如何解决这个问题呢?

既然是多线程引起的问题,那就要使用线程同步。我们在这里使用锁来实现。

  1. 1 using System;
  2. 2 using System.Threading;
  3. 3
  4. 4 public class SingletonTest
  5. 5 {
  6. 6 private static SingletonTest instance = null;
  7. 7
  8. 8 private static object lockObject = new object();
  9. 9
  10. 10 /// <summary>
  11. 11 /// 隐藏类的构造函数(定义私有构造函数)来确保类不能从外部实例化
  12. 12 /// </summary>
  13. 13 private SingletonTest()
  14. 14 {
  15. 15 Thread.Sleep(500);
  16. 16 Console.WriteLine("******单例类被实例化******");
  17. 17 }
  18. 18
  19. 19 /// <summary>
  20. 20 /// 使用静态函数轻松访问类实例
  21. 21 /// </summary>
  22. 22 /// <returns><see cref="SingletonTest"/></returns>
  23. 23 public static SingletonTest GetInstance()
  24. 24 {
  25. 25 // 双重检查锁定的方式实现单例模式
  26. 26 if (instance == null)
  27. 27 {
  28. 28 lock (lockObject)
  29. 29 {
  30. 30 if (instance == null)
  31. 31 {
  32. 32 instance = new SingletonTest();
  33. 33 }
  34. 34 }
  35. 35 }
  36. 36
  37. 37 return instance;
  38. 38 }
  39. 39
  40. 40 public void PrintSomething()
  41. 41 {
  42. 42 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
  43. 43 Console.WriteLine("Singleton Pattern Test");
  44. 44 }
  45. 45 }

上述代码在创建实例前,检查了两次实例是否为空,第一次判空是否有必要呢(上述代码26行)?为什么呢?我们想象这样的一种场景,在已经初始化类实例的情况下,是否还需要获取锁?答案是不需要,所以加第一个判断条件(上述代码26行)。

再次运行测试代码,结果如下:

 从结果来看,类只被实例化了一次,解决了多线程下类被多次实例化的问题。但是指令重排序是否对此有影响?是否需要volatile关键字?欢迎大佬来解答一下。

2、饿汉式单例(推荐)

第一种方式有点繁琐,可以简单点吗?如下

  1. 1 using System;
  2. 2 using System.Threading;
  3. 3
  4. 4 public class SingletonTest2
  5. 5 {
  6. 6 // 静态变量的方式实现单例模式
  7. 7 private static readonly SingletonTest2 Instance = new SingletonTest2();
  8. 8
  9. 9 private SingletonTest2()
  10. 10 {
  11. 11 Thread.Sleep(1000);
  12. 12 Console.WriteLine("******单例类被实例化******");
  13. 13 }
  14. 14
  15. 15 public static SingletonTest2 GetInstance()
  16. 16 {
  17. 17 return Instance;
  18. 18 }
  19. 19
  20. 20 public void PrintSomething()
  21. 21 {
  22. 22 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
  23. 23 Console.WriteLine("Singleton Pattern Test");
  24. 24 }
  25. 25 }

这种实现方式利用的是.NET中静态关键字static的特性,使单例类在使用前被实例化,并且只实例化一次,这个由.NET框架保证。

这种方式存在问题,在没有使用到类中的成员时候就创建实例了,能否在使用到类成员的时候才创建实例呢?如下图

  1. 1 using System;
  2. 2 using System.Threading;
  3. 3
  4. 4 public class SingletonTest2
  5. 5 {
  6. 6 // 静态变量的方式实现单例模式
  7. 7 private static readonly Lazy<SingletonTest2> Instance = new Lazy<SingletonTest2>(() => new SingletonTest2());
  8. 8
  9. 9 private SingletonTest2()
  10. 10 {
  11. 11 Thread.Sleep(1000);
  12. 12 Console.WriteLine("******初始化单例模式实例*****");
  13. 13 }
  14. 14
  15. 15 public static SingletonTest2 GetInstance()
  16. 16 {
  17. 17 return Instance.Value;
  18. 18 }
  19. 19
  20. 20 public void PrintSomething()
  21. 21 {
  22. 22 Console.WriteLine($"当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
  23. 23 Console.WriteLine("Singleton Pattern Test");
  24. 24 }
  25. 25 }

我们使用了Lazy关键字来延迟实例化。

四、例外

值得注意的是,反射会破坏单例模式,如下代码,能直接调用类的私有构造函数,再次实例化。

  1. 1 // 反射破坏单例
  2. 2 var singletonInstance = System.Activator.CreateInstance(typeof(SingletonTest2), true);

怎么避免呢?类的实例化都需要调用构造函数,那么我们在构造函数中加入判断标识即可。尝试实例化第二次的时候,就会抛异常。

  1. 1 private static bool isInstantiated;
  2. 2
  3. 3 private SingletonTest2()
  4. 4 {
  5. 5 if (isInstantiated)
  6. 6 {
  7. 7 throw new Exception("已经被实例化了,不能再次实例化");
  8. 8 }
  9. 9
  10. 10 isInstantiated = true;
  11. 11 Thread.Sleep(1000);
  12. 12 Console.WriteLine("******单例类被实例化******");
  13. 13 }

五、应用

那么实际应用中,哪些地方应该用单例模式呢?在这个类只应该存在一个对象的情况下使用。哪些地方用到了单例模式呢?

  • Windows任务管理器
  • HttpContext.Current

六、总结

俗话说,凡事都有两面性。单例模式确保了类只有一个实例,也引入了其他问题:

  • 单例模式中的唯一实例变量是使用static标记的,会常驻内存,不被GC回收,长期占用了内存
  • 在多线程的情况下,使用的都是同一个实例,所以需要保证类中的成员都是线程安全,不然可能会导致数据混乱的情况

代码下载: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

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号