【转】[C#] 单例模式

来自:阿里的 通义灵码

在C#中,单例模式实现线程安全时通常会采用双重检查锁定(Double-Checked Locking, DCL)的策略来确保只创建类的一个实例,并且这个过程是线程安全的。

两层lock的设计是为了减少获取锁的开销,尤其是当类的实例已经被初始化的情况下。

以下是一个使用双重检查锁定的C#单例模式示例:

public sealed class Singleton
{
    // 创建一个静态变量来保存唯一的 Singleton 实例。
    private static volatile Singleton instance;
    // 创建一个对象用于同步访问Singleton实例。
    private static object syncRoot = new Object();

    // 私有构造函数防止其他代码通过new关键字实例化。
    private Singleton() {}

    // 提供一个公共的静态方法来返回 Singleton 实例。
    public static Singleton Instance
    {
        get
        {
            // 第一层检查:如果实例已存在,则直接返回,无需加锁。
            if (instance != null)
            {
                return instance;
            }

            // 第二层检查:只有当第一次进入这里,即实例未被初始化时才需要加锁。
            lock (syncRoot)
            {
                // 在锁内再次检查实例是否已被另一个线程初始化。
                // 这是因为,在第一个if判断和进入lock之间可能存在竞态条件。
                if (instance == null)
                {
                    // 只有在这里创建实例。
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

为什么需要两层检查?

  1. 性能优化
    • 第一次检查instance != null可以在不加锁的情况下完成,如果实例已经初始化,那么大多数情况下可以直接返回实例,避免了不必要的锁操作带来的性能损耗。
  2. 线程安全性
    • 如果多个线程同时发现实例尚未创建,而只有一个线程能够进入内部的lock块进行初始化。如果没有第二次检查,所有线程都会创建实例,导致违反单例原则。
    • 由于CLR的内存模型允许对象的构造和初始化顺序与指令执行顺序不同步,因此即使在多线程环境中,也需要在lock保护下进行实例化以保证线程安全。第二层检查确保了实例化操作仅执行一次。

总结来说,双重检查锁定的目的在于既保证了线程安全,又尽量减少了对锁的使用频率,从而提高了程序的并发性能。

posted on 2024-03-22 17:37  z5337  阅读(6)  评论(1编辑  收藏  举报