.Net设计模式之单例模式

单例模式的介绍

在软件的开发过程中,很多时候,我们需要对一个类进行实例化后,再使用,有时这个类比较简单,有时也可能会很复杂,但不管怎样,为了保证软件的质量和效率,大多数时候,我们只希望它被实例化一次,所以这就需要引入单例模式(Singleton Pattern)了。单例模式,即保证一个类仅有一个实例,并提供一个访问它的全局访问点。那,怎么做到呢?为不不被实例化多个对象,就可以让类自身负责保存它的唯一实例。软件中重复使用某个类时,为了防止多次实例化产生的资源消耗,这个时候就应该使用单例设计模式了。如:网络请求、数据库操作等。


上面为单例的类图

单例要点

  1. 构造函数不对外开放,一般为 private;
  2. 通过一个静态方法或者枚举返回单例对象;
  3. 确保单例类的对象有且只有一个,尤其是在多线程环境下;
  4. 确保单例类对象在反序列化时不会被重新构建对象。

代码示例

简单实现

    public class Singleton
    {
        private static Singleton instance;

        /// <summary>
        /// 构造方法为private,可以僻免外界通过利用new创建此类实例的可能
        /// </summary>
        private Singleton() { }

        /// <summary>
        /// 此方法为获得本类实例的唯一全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance() 
        {
            if (instance == null)
            {
                return instance;
            }
            return instance;
        }
    }

上面的写法,可以实现单例模式,在单线程的情况下,并没有问题,但如果在多线程的环境下,两个或更多的线程同时判断 instance == null 为 true, 那么这个类仍然可以被多次实例化,那么它是不安全的,并不是真正的单例。这个时候,我们就会想到,可以在这之前,加上 lock 解决问题。提示代码如下:

线程安全

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

        /// <summary>
        /// 构造方法为private,可以僻免外界通过利用new创建此类实例的可能
        /// </summary>
        //private Singleton() { }

        /// <summary>
        /// 此方法为获得本类实例的唯一全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance() 
        {
            lock (locker)
            {
                if (instance == null)
                {
                    return instance;
                }
            }
            return instance;
        }
    }

lock( locker) // 在同一时刻加了锁的那部分程序,只有一个线程可以进入,其他线程要进入,只能待已进行的线程退出lock程序块后,才能进入,这就保证了线程安全,不会创建多个实例。现在线程安全是解决了,但还是有个小问题,每次获取实例的时候,都需要加锁,然后才能判断实例是否被创建了,怎么解决这个问题?这就需要再引入一个 双重锁 的做法了。

双重锁定

    /// <summary>
    /// 
    /// </summary>
    public class Singleton
    {
        private static readonly object locker = new object();
        private static Singleton instance;

        /// <summary>
        /// 构造方法为private,可以僻免外界通过利用new创建此类实例的可能
        /// </summary>
        //private Singleton() { }

        /// <summary>
        /// 此方法为获得本类实例的唯一全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance() 
        {
            // 判断1
            if (instance == null)
            {
                lock (locker)
                {
                    // 判断2
                    if (instance == null)
                    {
                        return instance;
                    }
                }
            }
            return instance;
        }
    }


上面的代码,在lock(locker) 前后,都加入了 对实例是否创建的判断, 判断1 主要是考虑到在实例已经被创建后,僻免程序再锁定 后面的程序块,减少资源的浪费,判断2 是为了 在没有创建实例的情况下才创建实例。经过多次改造后的单例,看似完美,但还是在获取实例的时候需要去判断实例是不是被创建了,那么,有没有别的方式每次都去判断呢?答案是有的,C#与公共语言运行库也提供了一种 "静态初始化" 方法,这种方法不需要开发人员显式地编写线程安全代码。

静态初始化

    /// <summary>
    /// 这里用到了 sealed 主要是不让其他类继承,而继承可能会增加实例
    /// </summary>
    public sealed class Singleton
    {
        //在第一次引用类的任何成员时创建实例,由公共语言运行库负责处理变量的初始化
        private static readonly Singleton instance = new Singleton();

        //构造函数私有化,是为了不被通过 new 达到实例化的目的
        private Singleton() { }

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

这个实现方式与前面的示例类似,都是解决了单例模式试图解决的两个基本问题:全局访问和实例化控制,公共静态属性是为访问实例提供了一个全局访问点。不同的地方在于它依赖公共语言运行库来初始化变量。再就是它的构造方法标记为私有,所以不能在类本身以外的地方通过 new 来实例化 Singleton 类;因此变量引用的是可以在系统中存在唯一的实例。值得注意的是,instance 变量标记为 readonly,也就是只能在静态初始化期间 或 在类的构造函数中分配变量。关于 sealed 的关键字,也可以在上面几个类中使用,只是类可能要稍加改动。

最后,感觉各位能花时间看到这里,谢谢,欢迎留言交流。

参考文献:

大话设计模式 程杰 著

posted @ 2022-04-02 14:35  鹅城小将  阅读(1012)  评论(0编辑  收藏  举报