设计模式-单例模式详解

一、引言

  单例模式应该算是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         }

五、应用

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

  • Windows任务管理器
  • HttpContext.Current

六、总结

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

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

代码下载:https://github.com/hzhhhbb/SingletonPattern

七、参考资料

原文地址:https://www.cnblogs.com/hzhhhbb/p/11373553.html

时间: 2024-10-10 11:07:49

设计模式-单例模式详解的相关文章

设计模式原则详解

我们在应用程序开发中,一般要求尽量两做到可维护性和可复用性.       应用程序的复用可以提高应用程序的开发效率和质量,节约开发成本,恰当的复用还可以改善系统的可维护性.而在面向对象的设计里面,可维护性复用都是以面向对象设计原则为基础的,这些设计原则首先都是复用的原则,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性. 面向对象设计原则和设计模式也是对系统进行合理重构的指导方针. 常用的面向对象设计原则包括7个,这些原则并不是孤立存在的,它们相互依赖,相互补充. 1.单一职责

【设计模式】设计模式原则详解

我们在应用程序开发中,一般要求尽量两做到可维护性和可复用性.       应用程序的复用可以提高应用程序的开发效率和质量,节约开发成本,恰当的复用还可以改善系统的可维护性.而在面向对象的设计里面,可维护性复用都是以面向对象设计原则为基础的,这些设计原则首先都是复用的原则,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性. 面向对象设计原则和设计模式也是对系统进行合理重构的指导方针. 常用的面向对象设计原则包括7个,这些原则并不是孤立存在的,它们相互依赖,相互补充. 1.单一职责

Java设计模式----观察者模式详解

[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3899208.html 联系方式:[email protected] [正文] 一.观察者模式的定义: 简单地说,观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象.这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新.例如:GUI中的事件

9种Java单例模式详解(推荐)

单例模式的特点 一个类只允许产生一个实例化对象. 单例类构造方法私有化,不允许外部创建对象. 单例类向外提供静态方法,调用方法返回内部创建的实例化对象.  懒汉式(线程不安全) 其主要表现在单例类在外部需要创建实例化对象时再进行实例化,进而达到Lazy Loading 的效果. 通过静态方法 getSingleton() 和private 权限构造方法为创建一个实例化对象提供唯一的途径. 不足:未考虑到多线程的情况下可能会存在多个访问者同时访问,发生构造出多个对象的问题,所以在多线程下不可用这种

【设计模式】单例模式详解

前言 博主只是一名大三学生,文章内容难免有不足之处,欢迎批评指正. 正文 转载请注明出处: http://blog.csdn.net/h28496/article/details/46403815 发 表 时 间: 2015年6月20日 单例模式的定义 一个类有且仅有一个实例,并且自行实例化向整个系统提供. 单例模式有三个要点: ① 这个类只能有一个实例: ② 必须是自行创建这个实例: ③ 必须是自行向整个系统提供这个实例. UML图: 单例模式的实现 假设一个场景:一个教师和一群学生在上课.学

Java设计模式图文详解

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周

23种设计模式整体详解

设计模式分为23种,每种都不是独立的,每种间都有联系: 下面从意图和使用性两方面详细描述每一种设计模式. 1.工厂模式(Factory) 意图 定义一个用于创建对象的接口,让子类决定实例化哪一个类.Factory Method 使一个类的实例化延迟到其子类. 适用性 当一个类不知道它所必须创建的对象的类的时候. 当一个类希望由它的子类来指定它所创建的对象的时候. 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候. 2.抽象工厂模式(Abst

[转]单例模式详解

3.1 单例模式的动机 对于一个软件系统的某些类而言,我们无须创建多个实例.举个大家都熟知的例子--Windows任务管理器,如图3-1所示,我们可以做一个这样的尝试,在Windows的"任务栏"的右键弹出菜单上多次点击"启动任务管理器",看能否打开多个任务管理器窗口?如果你的桌面出现多个任务管理器,我请你吃饭,(注:电脑中毒或私自修改Windows内核者除外).通常情况下,无论我们启动任务管理多少次,Windows系统始终只能弹出一个任务管理器窗口,也就是说在一个

Java 单例模式详解

概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,但只能有一个Printe