1

什么是单例模式?

保证一个类只有一个实例,并提供一个全局变量来访问这个实例,这就是单例模式,单例模式属于对象创建型模式。

单例模式的几个要素

  • 类只能有一个实例
  • 这个实例必须由该类本身创建
  • 该类必须向整个系统提供该实例的访问权限

单例模式的结构

image.png

单例模式的实现

class Singleton
{
    private static Singleton s_Instance;

    //私有构造方法,防止外部实例化
    private Singleton() { }

    public static Singleton GetInstance()
    {
        if (s_Instance == null)
            s_Instance = new Singleton();

        return s_Instance;
    }
}

测试代码

static void Main()
{
    var s1 = Singleton.GetInstance();
    var s2 = Singleton.GetInstance();
    if (object.ReferenceEquals(s1, s2))
        Console.WriteLine($"s1 s2两个实例是相同的");
    Console.ReadKey();
}

结果
image.png

饿汉式单例与懒汉式单例

饿汉式单例(最简单的单例)

image.png

class EagerSingleton
{
    //类加载时执行
    private static EagerSingleton s_Instance = new EagerSingleton();

    private EagerSingleton() { }

    public static EagerSingleton GetInstance()
    {
        return s_Instance;
    }
}

懒汉式单例

//懒汉式单例与饿汉式单例不同的在于:懒汉式单例在第一次被引用时将自己实例化(类被加载时不会实例化自身)
class LazySingleton
{
    private static LazySingleton s_Instance;

    //私有构造方法,防止外部实例化
    private LazySingleton() { }

    public static LazySingleton GetInstance()
    {
        if (s_Instance == null)
            s_Instance = new LazySingleton();

        return s_Instance;
    }
}

懒汉式单例的线程安全问题

在高并发多线程的环境下运行懒汉式单例的代码,会出现在某一时刻存在多个线程同时访问GetInstance方法,可能会创建多个实例,违背了单例模式的设计意图。

修改懒汉式单例代码使其线程安全

class LazySingleton
{
    private static readonly object s_LockObj = new object();

    private static LazySingleton s_Instance;

    //私有构造方法,防止外部实例化
    private LazySingleton() { }

    public static LazySingleton GetInstance()
    {
        if (s_Instance == null)
        {
            //加锁使程序在某一时刻只允许一个线程访问
            lock (s_LockObj)
            {
                if (s_Instance == null)
                    s_Instance = new LazySingleton();
            }
        }

        return s_Instance;
    }
}

为什么要双重校验

当实例不存在,并且多个线程同时调用GetInstance方法,此时它们都能通过第一重"s_Instance == null"的判断,此时只会有一个线程进入lock块执行创建代码,如果不进行第二次的"s_Instance == null"判断,当第一个线程创建完实例离开后,第二个线程进入lock块,由于第二个线程并不知道实例已经被创建,将继续创建新的实例,还是会产生多个实例对象。

饿汉式单例与懒汉式单例比较

饿汉式单例在类被加载时就创建实例对象,优点在于无需考虑线程安全问题,由于单例对象一开始就创建好了,所以调用速度优于懒汉式单例。同时,因为类加载时就需要创建单例对象,因此从资源利用角度来说,饿汉式单例不如懒汉式单例,类的加载时间相对懒汉式单例也更慢。

懒汉式单例在第一次使用时创建单例对象,实现了延迟加载,无须一直占用系统资源。但是懒汉式单例必须处理线程安全问题,这将导致系统性能受到一定程度的影响。

单例模式的优点

  • 系统中只存在一个对象,节约系统资源
  • 提供唯一一个实例,可以很好的控制如何访问以及何使访问它

单例模式的缺点

  • 没有抽象层,单例类扩展困难
  • 单例类将对象的创建和对象本身的功能耦合在一起,职责过重,在一定程度上违背了类的单一职责原则

在以下情况可以考虑使用单例模式

  • 某一个对象在系统中只需要一个或者因为实例化消耗太大只允许创建一个
  • 该实例只能通过一个公共访问点访问

DoubleJ
7 声望3 粉丝

« 上一篇
原型模式
下一篇 »
适配器模式