单例设计模式

Posted by JP on November 28, 2017

正文

一、什么是单例模式

1.生活中的单例

一个男人只能有一个媳妇「正常情况」,一个人只能有一张嘴,通常一个公司只有一个 CEO ,一个狼群中只有一个狼王等等

2.程序中的单例

一句话,就是保证一个类仅有一个实例即可「new 一次」,其实好多人都不把单例当作成一个设计模式,只是当作是一个工具类而已,因为它的确很简单,并且当你面视的时候面视官问你设计模式的时候估计都会说:可以说说你了解的设计模式吗「单例除外」。虽然很简单,但是我们还是要掌握和了解它,并且要深层次的了解它。

单例模式的定义

单例单例就是单一的实例,官方定义: 保证一个类只有一个实例,并提供一个全局访问点。

单例的应用

  • 网站的计数器
  • 应用配置
  • 多线程池一般也采用单例去设计
  • 数据库配置,数据库连接池
  • window窗体不能同时出现两个
  • 其它等等
1.单例模式的几种实现方式
1.Lazy模式(懒汉式)线程不安全

懒汉式单例模式,是在我需要的时候才去初始化实例,也就是说在类加载的时候,静态成员变量是 null 的,只有需要它的时候才去初始化实例,所以懒汉式可以延时加载

特点

  • 1、线程不安全
  • 2、延时初始化类,在我需要的时候「也就调用 GetInstance」的时候才去初始化

优缺点

  • 优点:延时初始化类,省资源,不想用的时候就不会浪费内存
  • 缺点:线程不安全,多线程操作就会有问题

演示代码

    /// <summary>
    ///  Singleton mode implementation
    /// </summary>
    public class Singleton
    {
        //define a static variable to hold an instance of the class
        private static Singleton instance;

        //define a private constructor,so that the outside can't cteate an instanse of the class
        private Singleton() { }

        /// <summary>
        ///  define public methods to provide a global access point, and you can also define public properties to provide global access points
        /// </summary>
        public static Singleton GetInstance()
        {
            // create if the instance of the class does not exist, otherwise return directly
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }

解析如下:

  1)首先,该Singleton的构造函数必须是私有的,以保证客户程序不会通过new()操作产生一个实例,达到实现单例的目的;

  2)因为静态变量的生命周期跟整个应用程序的生命周期是一样的,所以可以定义一个私有的静态全局变量instance来保存该类的唯一实例;

  3)必须提供一个全局函数访问获得该实例,并且在该函数提供控制实例数量的功能,即通过if语句判断instance是否已被实例化,如果没有则可以同new()创建一个实例;否则,直接向客户返回一个实例。

  上面单例模式的实现在单线程下确实是完美的,但没有考虑线程并发获取实例问题,在多线程的情况下会得到多个Singleton实例,因为两个线程同时运行GetInstance方法时,此时两个线程判断(instance==null)这个条件都成立,此时两个线程就都会创建Singleton的实例,违反了单例规则。既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使GetInstance方法在同一时间只让一个线程运行就好了。因此,需对上面代码修改,看下面

2、Lazy模式(懒汉式)线程安全

懒汉式线程安全比懒汉式线程不安全多了一个线程安全

特点

  • 1、线程安全
  • 2、延时初始化类,在我需要的时候「也就调用 GetInstance」的时候才去初始化

优缺点

  • 优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全
  • 缺点:虽然线程安全,但是加了锁对性能影响非常大「相当于排队获取资源,没有拿到锁子就干等」

代码演示

 	/// <summary>
    ///  Singleton mode implementation
    /// </summary>
    public class Singleton
    {
        //define a static variable to hold an instance of the class
        private static Singleton instance;

        // define an identity to ensure thread synchronization
        private static readonly object locker = new object();

        //define a private constructor,so that the outside can't cteate an instanse of the class
        private Singleton() { }

        /// <summary>
        ///  define public methods to provide a global access point, and you can also define public properties to provide global access points
        /// </summary>
        public static Singleton GetInstance()
        {
            // create if the instance of the class does not exist, otherwise return directly

            //When the first thread runs here, the locker object is "locked" at this time.
            //When the second thread runs the method, 
            //it first detects that the locker object is in the "locked" state, 
            //and the thread will hang waiting for the first thread to unlock.
            //After the lock statement finishes running (that is, after the thread finishes running), it will "unlock" the object.

            //Double lock only needs one judgment.
            if (instance == null)
            {
                lock (locker)
                {
                    // create if the instance of the class does not exist, otherwise return directly
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                    return instance;
                }

            }
        }
    }

上述代码使用了双重锁方式较好地解决了多线程下的单例模式实现。先看内层的if语句块,使用这个语句块时,先进行加锁操作,保证只有一个线程可以访问该语句块,进而保证只创建了一个实例。再看外层的if语句块,这使得每个线程欲获取实例时不必每次都得加锁,因为只有实例为空时(即需要创建一个实例),才需加锁创建,若果已存在一个实例,就直接返回该实例,节省了性能开销。
到这里,有的同学可能会有疑问,lock语句内层的if(instance==null)感觉是多余的,其实一开始一看我也觉得是多余的,仔细一想,这句代码是不能省的,两个线程同时运行lock外层的if (instance == null),都成立,第一条线程加锁实例化一个对象,解锁后,如果不加判断,第二条线程直接实例化一个对象,这就不是单例了。

3、饿汉模式

饿汉式单例模式如其名,是一个饿货,类的实例在类加载的时候就初始化出来「把这一过程当作一个汉堡,也就是说必须要把汉堡提前准备好,饿货就知道吃」, 这种模式的特点是自己主动实例。

特点

  • 1、是线程安全的
  • 2、类不是延时加载「直接是类加载的时候就初始化」

优缺点

  • 优点:没有加锁,执行效率非常高「其实是以空间来换时间」
  • 缺点:在类加载的时候就会初始化,浪费内存「你知道我要不要使用这个实例吗,你就给我初始化,太任性了」

演示代码

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton GetInstance()
    {
        return instance;
    }
}

上面使用的readonly关键可以跟static一起使用,用于指定该常量是类别级的,它的初始化交由静态构造函数实现,并可以在运行时编译。在这种模式下,无需自己解决线程安全性问题,CLR会给我们解决。由此可以看到这个类被加载时,会自动实例化这个类,而不用在第一次调用GetInstance()后才实例化出唯一的单例对象。

Reference:
1.C#设计模式(1)——单例模式
2.人人都会设计模式:单例模式–SingleTon
3.C#设计模式学习笔记-单例模式
4.在C#中实现 Singleton