单例模式-设计模式(C#)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luckystar_99/article/details/82716135
什么是单例模式

单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点。

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;售票时,一共有100张票,可有有多个窗口同时售票,但需要保证不要超售(这里的票数余量就是单例,售票涉及到多线程)。如果不是用机制对窗口对象进行唯一化将弹出多个窗口,如果这些窗口显示的都是相同的内容,重复创建就会浪费资源。


优点

  • 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能

缺点

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。

单例模式结构图

这里写图片描述


单例模式第一版

    class Singleton
    {
        private static Singleton instance;
        //构造方法让其private,这就堵死了外界利用new创建此类实例的可能
        private Singleton()
        {
        }
        //此方法是获得本类实例的唯一全局访问点
        public static Singleton GetInstance()
        {
            //若实例不存在,则new一个新实例,否则返回已有的实例
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }   

为什么这样写呢?我们来解释几个关键点:


  1. 要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。

  2. instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。

  3. getInstance是获取单例对象的方法。
    如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式
    如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式
    这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。


但是刚才的代码不是线程安全,为什么说刚才的代码不是线程安全呢?


假设Singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getInstance方法:

这里写图片描述

因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作:

这里写图片描述
这样一来,显然instance被构建了两次。让我们对代码做一下修改:


单例模式第二版

public class Singleton {
    private Singleton() {}  //私有构造函数
   private static Singleton instance = null;  //单例对象
   //静态工厂方法
   public static Singleton getInstance() {
        if (instance == null) {      //双重检测机制
         synchronized (Singleton.class){  //同步锁
           if (instance == null) {     //双重检测机制
             instance = new Singleton();
               }
            }
         }
        return instance;
    }
}

为什么这样写呢?我们来解释几个关键点:


  1. 为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。

  2. 进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

像这样两次判空的机制叫做双重检测机制


但是我们的这段代码仍然不是绝对的线程安全


假设这样的场景,当两个线程一先一后访问getInstance方法的时候,当A线程正在构建对象,B线程刚刚进入方法:

这里写图片描述


这种情况表面看似没什么问题,要么Instance还没被线程A构建,线程B执行 if(instance == null)的时候得到false;要么Instance已经被线程A构建完成,线程B执行 if(instance == null)的时候得到true。

真的如此吗?答案是否定的。这里涉及到了JVM编译器的指令重排。小编就不再赘述了,(因为小编也不懂),大家自己研究一下吧。

可以参考博客什么是单例模式

猜你喜欢

转载自blog.csdn.net/luckystar_99/article/details/82716135
今日推荐