美文网首页编程语言爱好者C#.NET
《C#设计模式》-单例模式

《C#设计模式》-单例模式

作者: 张中华 | 来源:发表于2020-05-02 21:55 被阅读0次

    单例模式是结构最简单地设计模式,在它地核心结构中只包含一个被称为单例类地特殊类。

    单例模式概述

    对于一个软件系统中地某些类而言,只有一个实例很重要。例如:一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID生成器。在Windows操作系统中只能打开一个任务管理器窗口。如果不使用机制对窗口对象进行唯一化,势必会弹出多个窗口。如果这些窗口显示地内容完全移值,则是重复对象,浪费内存资源;如果这些窗口显示内容不一致,则意味着某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此,有时确保系统中某个对象的唯一性非常重要。

    定义

    单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
    单例模式有3个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

    单例模式的结构与实现

    单例模式UML
    单例模式的实现

    单例模式的目的是保证一个类有且仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,就是单例类-Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。
    单例模式的实现代码如下:

    public class Singleton
        {
            private static Singleton instance = null; // 静态私有成员变量
    
            // 私有构造函数
            private Singleton() { }
    
            // 静态公有工厂方法,返回唯一实例
            public static Singleton GetInstance() {
                if(instance == null)
                {
                    instance = new Singleton();
                }
    
                return instance;
            }
        }
    

    测试:

     class Program
        {
            static void Main(string[] args)
            {
                // 单例测试
                var s1 = Singleton.GetInstance();
                var s2 = Singleton.GetInstance();
                // 判断两个对象是否相同
                if (s1 == s2)
                {
                    Console.WriteLine("两个对象实例相同。");
                }
                else 
                {
                    Console.WriteLine("两个对象实例不同"); 
                }
    
                Console.ReadLine();
            }
        }
    
    测试结果

    在单例模式的实现过程中,用户需要注意以下3点:

    1. 单例类构造函数的可见性为private
    2. 提供一个类型为自身的静态私有成员变量
    3. 提供一个公有的静态工厂方法

    饿汉式单例与懒汉式单例

    饿汉式单例

    饿汉式单例类(Eager Singleton)是实现起来最简单的单例类,结构如下:



    实例代码如下:

        public class EagerSingleton
        {
            private static EagerSingleton instance = new EagerSingleton();
    
            private EagerSingleton() { }
    
            public static EagerSingleton GetInstance()
            {
                return instance;
            }
        }
    
    测试结果

    当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。

    懒汉式单例类与双重检查锁定

    饿汉式单例类在第一次被引用时将自己实例化,懒汉式单例类加载时不会自己实例化。
    在懒汉式单例类中,不是在定义静态变量时实例化单例类,而是在第一次调用静态工厂方法时实例化单例类。
    但是懒汉式单例存在一个很严重的问题:如果在高并发、多线程环境下实现懒汉式单例类,在某一个时刻可能会有多个线程需要使用单例对象,即会有多个线程同时调用GetInstace()方法,可能会造成创建多个实例对象,这将违背单例模式的设计意图。为了阻止生成多个单例对象,需要使用C#语言中的lock关键字,lock关键字锁定的代码片段称为临界区,可以确保当一个线程位于代码的临界区时,另一个线程不能进入临界区。如果其他线程试图进入锁定的代码,则将一直等待,直到该对象被释放为止。
    代码实例:

        public class LazySingleton
        {
            private static LazySingleton instance = null;
    
            private LazySingleton() { }
    
            public static LazySingleton GetInstance()
            {
                if (instance == null)
                {
                    instance = new LazySingleton();
                }
    
                return instance;
            }
        }
    
    测试结果

    加了双重锁定代码示例:

        public class LazySingleton
        {
            private static LazySingleton instance = null;
            // 程序运行时创建一个静态只读的辅助对象
            private static readonly object syncRoot = new object();
    
            private LazySingleton() { }
            public static LazySingleton GetInstance()
            {
                // 第一重判断, 先判断实例是否存在,不存在再加锁处理
                if (instance == null)
                {
                    // 加锁的程序再某一时刻只允许一个线程访问
                    lock (syncRoot)
                    {
                        // 第二重判断
                        if (instance == null)
                        {
                            instance = new LazySingleton();
                        }
                    }
                }
    
                return instance;
            }
        }
    
    测试结果

    为了更好的对单例对象的创建进行控制,此处使用了一种被称为双重检查锁定(Double-Check Locking)的双重判断机制。再双重检查锁定中,当实例不存在且同时又两个线程调用GetInstance()方法时,它们都可以通过第一重“instance == null” 判断,并且由于lock锁定机制,只有一个线程进入lock中执行创建代码,另一个线程处于排队等待状态,必须等待第一个线程执行完毕才可以进入lock锁定的代码,如果此时不进行第二重“instance == null”判断,第二个线程并不知道实例已经创建,将继续创建新的实例,还是会产生多个单例对象,违背了单例模式的设计思想,因此需要进行双重检查。

    饿汉式单例类于懒汉式单例类比较
    • 饿汉式单例类在加载时就将自己实例化,它的优点在于无须考虑多个线程同时访问的问题,可以确保实例的唯一性;
    • 从调用的速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论在系统运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。
    • 懒汉式单例类在第一个使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然会涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的概率变大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。

    单例模式的优缺点与适用环境

    单例模式作为一种明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。

    单例模式的优点
    • 提供了唯一实例的受控访问
    • 由于系统内存中只存在一个对象,节约了系统资源
    • 单例模式允许可变数目的实例。基于单例模式的扩展,指定个数的实例对象,既节省系统资源,又解决了由于单例对象共享过多有损性能的问题。
    单例模式的缺点
    • 没有抽象层,扩展困难
    • 职责过重,在一定程度上违背了单一职责原则
    • 现在很多面向对象语言的运行都提供了自动垃圾回收技术,因此,实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,在下次利用时又将重新实例化,这将导致共享的单例对象状态丢失。
    单例模式使用环境
    • 系统只需要一个实例对象。
    • 客户调用类的单个实例只允许使用一个公共访问点。

    如果觉得文章写得还行,请点个赞。如果想与我进一步交流,可以关注我的公众号或者加我的微信。

    个人微信
    公众号_DotNet微说.jpg

    相关文章

      网友评论

        本文标题:《C#设计模式》-单例模式

        本文链接:https://www.haomeiwen.com/subject/ywnxghtx.html