非线程安全单例的危险是什么?

What are the dangers of non thread safe singleton?

最近我发现 this article 解释了如何在 C# 中实现 thread safe singleton pattern。 但是,我想知道使您的单例线程安全有多重要以及让您的单例 class 非线程安全的危险是什么。考虑一个非常简单的非线程安全的单例模式实现:

public class ThreadNotSafeSingleton
{
    public static ThreadNotSafeSingleton Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new ThreadNotSafeSingleton();
            }

            return _instance;
        }
    }

    private static ThreadNotSafeSingleton _instance;

    private ThreadNotSafeSingleton()
    {
    }
}

现在说一下,由于这段代码是非线程安全的,所以会有两个线程进入这行代码_instance = new ThreadNotSafeSingleton();。在这种情况下,第一个线程将初始化 _instance 字段,然后第二个线程将再次初始化 _instance 字段,结果是您的应用程序中将只有一个 _instance

那么这里有什么大不了的呢?我知道,如果您有更复杂的代码,并且您的构造函数运行一些其他可能不是线程安全的代码,那可能很危险。但是如果你的单例那么简单,非线程安全有什么问题吗?

由于单例模式将 class 限制为只有一个实例,您的方法违反了该模式。

如果出于任何原因这对您的实施来说是可行的,并且我们遵循您陈述的示例,那么两个并发访问中的每一个都很有可能获得 ThreadNotSafeSingleton 的另一个实例。如果编译器决定不需要回读它在返回之前写入的 _instance 变量,则可能由于优化而发生这种情况。此优化行为由 C# 实现的内存模型定义。

volatile 关键字经常被引用为可能的解决方案,但它不会解决同步问题(正如 BionicCode 所指出的),当线程 1 通过 if (_instance == null)行并进入睡眠状态,然后线程 2 也评估相同的 if 并开始实例化单例。当线程 1 稍后醒来时,它将实例化另一个单例。

Singleton 模式的目的是从一种类型中仅创建一个对象。

假设 thread1thread2 第一次访问 Instance 属性 get 访问器 (_instance还是null) 同时:

thread1 检查 if (_instance == null) 表达式,发现 _instancenull,同时 thread2 检查 if 表达式并发现 _instance 也是 nullthread1 来到这个 _instance = new ThreadNotSafeSingleton(); 表达式并从 ThreadNotSafeSingleton 类型创建一个新对象。因为 thread2 检查 if 表达式并发现 _instance 也是 null,所以来到 if 代码块并创建一个将覆盖引用的新对象_instance 变量。最后,thread1 使用了一个不同于 thread2 的对象。

_instancenull 并且两个或多个线程同时尝试获取您的类型的实例时,会出现此问题。您必须限制线程同时访问 if 代码体(线程同步),尤其是当您的 _instacenull 时。