欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

我们真的会使用单例模式吗?

程序员文章站 2022-07-02 14:30:10
这篇博客的标题用了一个疑问句,源于我们公司的代码评审,深刻的讨论了单例模式的使用场景及其与静态方法来说有何不同,这次讨论确实让我真正的理解了单例模式的使用,虽然说理解还一定全面,但必须作为一个认知的提升。告诉了我自己,对于编程,不懂的太多,原理性的东西还需要持续的学习。 进入正文,我们来讨论一下,什 ......

这篇博客的标题用了一个疑问句,源于我们公司的代码评审,深刻的讨论了单例模式的使用场景及其与静态方法来说有何不同,这次讨论确实让我真正的理解了单例模式的使用,虽然说理解还一定全面,但必须作为一个认知的提升。告诉了我自己,对于编程,不懂的太多,原理性的东西还需要持续的学习。

进入正文,我们来讨论一下,什么是单例模式,何时使用单例模式?

单例模式是经典设计模式的一种,熟悉设计模式或者说读过设计模式相关书籍的同事都知道,这应该算是设计模式中最简单、最容易理解、使用最广泛的一种。单例模式主要是用来实现一个类的实例全局唯一,使用double check的形式来定义。


 1 public class SingleInstance
 2     {
 3         private static readonly object _lock = new object();
 4         private static SingleInstance _instance = null;
 5 
 6         /// <summary>
 7         /// 私有构造函数
 8         /// </summary>
 9         private SingleInstance() { }
10 
11         /// <summary>
12         /// 单一实例
13         /// </summary>
14         /// <returns></returns>
15         public static SingleInstance GetInstance()
16         {
17             if (_instance == null)
18             {
19                 lock (_lock)
20                 {
21                     if (_instance == null)
22                     {
23                         _instance = new SingleInstance();
24                     }
25                 }
26             }
27             return _instance;
28         }
29 
30 
31         public  void Show()
32         {
33             Console.WriteLine("输出。。。郭志奇");
34         }
35 
36         public  void Speak()
37         {
38             Console.WriteLine("说话。。。郭志奇");
39         }
40     }

单例模式使用了私有构造函数来保证外部无法实例化、使用double check来保证实例被唯一创建。这是一个基本的单例模式写法,我一般会在其中写一些方法来进行调用,主要是为了避免每次调用都需要new的麻烦。但其中存在一些问题,如果采用静态方法来写:
1        public static void Show()
2         {
3             Console.WriteLine("输出。。。郭志奇");
4         }
5 
6         public static void Speak()
7         {
8             Console.WriteLine("说话。。。郭志奇");
9         }

 

比较这两种调用,其实使用方式是一致的,但单例模式会在程序运行中一直存在,不会被销毁,因为单例模式中使用到了静态变量,静态变量的使用会导致实例不会被销毁。但这也不应该是单例模式的缺点。

但我为什么会说我们真的懂单例模式?

回到开头,我们说单例模式,为什么我们需要单例模式,绝对不是因为方便调用,因为静态方法更方便。那到底为什么使用单例模式呢?其实经过我们的讨论,单例模式的使用场景是一些全局不可变参数,可以放到单例中,比如从配置获取值,然后缓存到单例中,这才是我们应当使用单例的场景,千万别像我,为了使用方便而无节制的使用单例。

使用单例,方便调用,但会造成什么问题呢?

要回答这个问题,我们首先回忆一下GC的垃圾回收机制,垃圾回收分为三代,如果类中包含静态成员,垃圾回收机制是不会回收的,也就意味着如果我们无节制的使用单例,会造成程序运行过程中出现大量的实例不会被销毁,会无意识的造成内存使用增高。    如果采用懒加载的方式,在单例未被调用的时候,不会实例化,如果采用饿汉加载的话,那么在程序初始化的时候,就会被初始化,无疑会加重程序的初始化成本,增加启动时间。

如果我们仅仅是为了方便调用,可以使用静态方法。

上面我们说了懒加载方式,我们来代码说明一下饿汉模式的加载方式:

 1   public class SingleInstance
 2     {
 3         private static readonly object _lock = new object();
 4         private static SingleInstance _instance = new SingleInstance();
 5 
 6         /// <summary>
 7         /// 私有构造函数
 8         /// </summary>
 9         private SingleInstance() { }
10 
11         /// <summary>
12         /// 单一实例
13         /// </summary>
14         /// <returns></returns>
15         public static SingleInstance GetInstance()
16         {            
17             return _instance;
18         }
19 
20 
21         public void Show()
22         {
23             Console.WriteLine("输出。。。郭志奇");
24         }
25 
26         public void Speak()
27         {
28             Console.WriteLine("说话。。。郭志奇");
29         }
30     }

饿汉模式的加载就是静态成员在定义的时候即初始化。

总结:

1、我们应该选择合适的时机使用单例模式,不要无节制的使用,应该明白何时才应该使用单例模式。

2、尽量避免静态成员的使用,因为静态成员所在的实例,不会被GC回收。

3、优先选择静态方法调用而不是单例模式调用。

4、如果必须使用单例模式,尽量采用懒加载,而不是饿汉加载的方式,减少程序启动成本。

引申:

1、我们使用了lock(object)来锁定一个变量,达到加锁的目的,避免多个线程同时对实例执行初始化。那么如果我们lock(string 字符串类型)是否可以呢?答案是否定。

2、System.String和string有什么不同呢?

欢迎有不同见解的同事可以回复讨论,知识总是在讨论中得到升华。