非线程安全单例的危险有哪些?

时间:2019-06-22 08:33:29

标签: c# .net design-patterns thread-safety singleton

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

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存在。

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

2 个答案:

答案 0 :(得分:3)

由于单例模式将类限制为仅一个实例,因此您的方法违反了该模式。

如果出于某种原因这对于您的实现来说是可以的,并且我们遵循您陈述的示例,则两个并发访问中的每一个都将获得另一个ArrayList <Person> list = new ArrayList<Person>(); 实例的可能性很高。如果编译器认为不需要在返回之前回读刚写入的ThreadNotSafeSingleton变量,则可能由于优化而发生这种情况。这种优化行为由C#实现的内存模型定义。

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

答案 1 :(得分:3)

Singleton模式的目的是根据Type创建仅一个对象。

想象一下thread1thread2首次进入get属性的Instance访问器 ({{1}仍_instance)同时

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

thread2_instance且两个或多个线程尝试同时获取您的Type实例时,会发生此问题。您必须限制同时访问线程的null代码主体(线程同步),尤其是当if_instace时。