设计模式(2) 单例模式

  • 单例模式
  • 线程安全的Singleton
  • 会破坏Singleton的情况
  • 线程级Singleton

单例模式是几个创建型模式中最独立的一个,它的主要目标不是根据客户程序调用生成一个新的实例,而是控制某个类型的实例数量只有一个。
GOF对单例的描述为:
Ensure a class only has one instance, and provide aglobal point of access to.
—Design Patterns : Elements of Reusable Object-Oriented Software

单例模式

单例模式的应用场景不必赘述,先来一个最简单的实现方式:

public class Singleton
{
    private Singleton() { }
    private static Singleton instance;
    public static Singleton Instance()
    {
        if (instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}

这里采用的是Lazy方式,也可以在静态变量被创建的时候直接初始化实例。
这段代码已经可以满足最初Singleton模式的设计要求,在大多数情况下可以很好地工作。但在多线程环境下这种实现方式是存在缺陷的,当多个线程几乎同时调用Singleton类的Instance静态属性的时候,instance成员可能还没有被实例化,因此它被创建了多次,而且最终Singleton类中保存的是最后创建的那个实例,各个线程引用的对象不同。

线程安全的Singleton

为了保证多线程环境下instance实例只有一个,对代码进行了优化:

public class Singleton
{
    private static volatile Singleton instance;
    public static Singleton Instance()
    {
        if (instance == null)
        {
            lock (typeof(Singleton))
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

相比最初的实现,改变的地方有这几处:

  • instance使用volatile关键字修饰,它表示字段可能被多个并发执行的线程修改。
  • 在实例化前lock Singleton类型,避免了多个线程同时实例化的问题。
  • 第一个if加在了lock之前,是为了避免每次调用都锁定Singleton类型带来的效率下降。
  • lock后再次判断instance是否为空,是因为在高并发场景下,在第一个线程锁定并实例化期间,仍然可能会有别的线程进入到第一层if内,这样如果不再次判空,就会重复实例化。

会破坏Singleton的情况

有些情况会破坏Singleton的封装,跳过“只能有一个实例”的限制,在实际应用中要注意规避。

  • 第一种情况就是实现ICloneable接口或继承自其相关的子类,这样客户程序借助ICloneable接口就可以跳过已经被隐藏起来的构造函数

  • 另外通过二进制、Json之类序列化、反序列化的方式也可以产生新的对象。

线程级Singleton

前面讨论的是线程安全的Singleton实现,但有时需要的是更细粒度的Singleton,比如线程级的Singleton,只要保证在一个线程内只有一个实例即可,这就类似Asp.NET Core 自带的IOC提供的AddScope注册方式,可以保证一个HttpContext内只有一个实例。

虽然Asp.NET Core提供类似的现成实现,但如果在非Web环境下也需要线程级的实例控制该怎么办呢? 结合C#提供的System.ThreadStaticAttribute可以完成

通过System.ThreadStaticAttribute可以将某个静态变量限定为仅在本线程内部是静态的。
实现如下:

public class ThreadSingleton
{
    private ThreadSingleton() { }

    [ThreadStatic] //instance只在当前线程内为静态
    private static ThreadSingleton instance;
    public static ThreadSingleton Instance()
    {
        if (instance == null)
        {
            instance = new ThreadSingleton();
        }
        return instance;
    }
}

这里再不需要线程锁了,因为线程级的单例不需要考虑线程安全。
为了验证实现的准确性,首先构造一个线程内执行的目标对象:

class Work
{
    public static IList<int> Log = new List<int>();

    /// <summary>
    /// 每个线程的执行部分
    /// </summary>
    public void Procedure()
    {
        ThreadSingleton s1 = ThreadSingleton.Instance();
        ThreadSingleton s2 = ThreadSingleton.Instance();

        //证明可以正常构造实例
        Assert.IsNotNull(s1);
        Assert.IsNotNull(s2);

        //验证当前线程执行体内两次获取的是同一个实例
        Assert.AreEqual(s1.GetHashCode(), s2.GetHashCode());

        //记录当前线程所使用对象的HashCode
        Log.Add(s1.GetHashCode());
    }
}

这个类会在每个线程内部执行,并验证线程内多次获取的Instance是同一个实例,并记录这个实例的HashCode,以便与别的线程实例对比。
接下来开启多个线程同时执行Procedure()方法:

[Test]
public void ThreadSingletonTest()
{
    int threadCount = 4;
    Thread[] threads = new Thread[threadCount];  //创建4个线程
    for (int i = 0; i < threadCount; i++)
    {
        ThreadStart work = new ThreadStart(new Work().Procedure);
        threads[i] = new Thread(work);
    }

    //执行线程
    foreach (var thread in threads)
    {
        thread.Start();
    }

    Thread.Sleep(10000);
    Assert.AreEqual(threadCount, Work.Log.Distinct().Count());
}

Work类的静态变量Log中记录了每个线程中实例的HashCode,这些HashCode彼此不相同,且与线程的数量一致,证明每个线程间的实例是不相同的。

参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》

(0)

相关推荐

  • 大话设计模式笔记(十八)の单例模式

    单例模式 定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象.一个最好的办法就是,让类自身负责保存它的唯一实例.这 ...

  • 设计模式:单例模式 (关于饿汉式和懒汉式)

    定义 单例模式是比较常见的一种设计模式,目的是保证一个类只能有一个实例,而且自行实例化并向整个系统提供这个实例,避免频繁创建对象,节约内存. 单例模式的应用场景很多, 比如我们电脑的操作系统的回收站就 ...

  • 23种设计模式入门 -- 单例模式

    单例模式:采用一定的方法,使得软件运行中,对于某个类只能存在一个实例对象,并且该类只能提供一个取得实例的方法. 分类: 饿汉式 静态常量方式 静态代码块方式 懒汉式 普通方式,线程不安全 同步方法方式 ...

  • Singleton单例模式

    Singleton单例模式

  • 图解Java设计模式之单例设计模式

    图解Java设计模式之单例设计模式 设计模式介绍 设计模式类型 单例设计模式介绍 饿汉式(静态常量) 饿汉式(静态代码块) 懒汉式(线程不安全) 懒汉式(线程安全,同步方法) 懒汉式(线程安全,同步代 ...

  • 设计模式-创建型-单例模式

    前言: 单例模式,顾名思义,只存在一个实例.官方定义:对于类的单例模式设计,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法). ...

  • java常见设计模式之---单例模式

    java常见设计模式之---单例模式 1.单例模式简介 应用场景举例 2.单例模式的特点 3.单例模式和静态类 4.单例模式的经典实现 饿汉式单例(典型实现) 饿汉式-静态代码块 懒汉式单例创建,五种 ...

  • 面试高频-吃透单例设计模式

    单例设计模式 单例设计模式的介绍 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法). 比如 Hiber ...

  • 【转】C#设计模式-单例模式(Singleton Pattern)

    目录 介绍 第一个版本 --不是线程安全的 第二个版本 -- 简单的线程安全 第三个版本 - 使用双重检查锁定尝试线程安全 第四个版本 - 不太懒,不使用锁且线程安全 第五版 - 完全懒惰的实例化 第 ...

  • 设计模式之单例模式(Singleton Pattern)

    一.定义 一个类只有一个实例,且该类能自行创建这个实例的一种模式. 二.单例模式举例 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各 ...

  • PHP设计模式之单例模式

    PHP设计模式之单例模式 单例模式绝对是在常用以及面试常问设计模式中排名首位的.一方面它够简单,三言两语就能说明白.另一方面,它又够复杂,它的实现不仅仅只有一种形式,而且在Java等异步语言中还要考虑 ...

  • [PHP小课堂]PHP设计模式之单例模式

    [PHP小课堂]PHP设计模式之单例模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 【设计模式】单例模式(Singleton Pattern)

    懒汉式 public class Singleton { private static Singleton instance; private Singleton() {}; public stati ...

  • 设计模式之单例模式

    目录: 什么是单例模式 单例模式的应用场景 单例模式的优缺点 单例模式的实现 总借 一.什么是单例模式 单例模式顾名思义就是只存在一个实例,也就是系统代码中只需要一个对象的实例应用到全局代码中,有点类 ...

  • 设计模式之单例模式(Singleton Pattern),太简单了

    基本定义 单例模式就是确保某一个类只有一个实例,并且提供一个全局访问点.单例模式有如下几个特点: 它只有一个实例. 它必须要自行实例化. 它必须自行向整个系统提供访问点. 代码实现 饿汉式 直接初始化 ...

  • Java设计模式之单例模式

    单例模式,是特别常见的一种设计模式,因此我们有必要对它的概念和几种常见的写法非常了解,而且这也是面试中常问的知识点. 所谓单例模式,就是所有的请求都用一个对象来处理,如我们常用的Spring默认就是单 ...