baidixing 阅读(4) 评论(0)

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

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

单例模式是经典设计模式的一种,熟悉设计模式或者说读过设计模式相关书籍的同事都知道,这应该算是设计模式中最简单、最容易理解、使用最广泛的一种。单例模式主要是用来实现一个类的实例全局唯一,使用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有什么不同呢?

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