使用setter时,何时应将静态实例锁定在多线程单例中?

时间:2019-03-27 02:31:52

标签: c# multithreading thread-safety

尝试了解何时将锁定静态变量视为最佳实践。静态实例设置器是线程安全的吗?如果不是,应该是,为什么(不使其成为线程安全的后果是什么?)?

class MyClass
{
    private static MyClass _instance;

    private static readonly object _padlock = new object();

    public static MyClass Instance
    {
        get
        {
            if(_instance == null)
            {
                lock(_padlock)
                {
                    if(_instance == null)
                    {
                        _instance = new MyClass();
                    }
                }
            }
            return _instance;
        }
        set => _instance = value;
    }

}

2 个答案:

答案 0 :(得分:3)

这称为双重检查锁定

但是,双重检查锁定要求基础字段为volatile 1

简而言之,分配是原子的,但是需要在不同的内核/ CPU之间进行同步(完全隔离,通过锁)。另一个内核同时读取该值的原因可能是缓存了过期的值 1

有几种方法可以使代码具有线程安全性:

  • 避免双重检查锁定,只需执行lock语句中的所有操作即可。
  • 使用volatile关键字使字段易变
  • 使用Lazy类,该类保证是线程安全的

注意:完全不受保护的二传手进一步增加了复杂性 3 ..

但是,在您的情况下,使用双重检查锁定可能只需要一次检查并通过volatile字段锁定就可以了,但是我认为您最好的选择是完全lock一切安心

public static MyClass Instance
{
    get
    {
         lock(_padlock)
         {
             if(_instance == null)
                 _instance = new MyClass();
             return _instance;
         }

    }
    set 
    {
         lock(_padlock)
         {
             _instance = value;
         }
    } 
}

注意 :是的,这将导致性能下降


参考


其他资源

答案 1 :(得分:1)

在我看来,(在setter上)锁定还是不锁定,您总是会遇到时间问题。想象一下这些情况:

  1. 您在setter上有一个锁,但是在使用该锁之前会调用getter。调用者获取旧实例。
  2. 您在setter上有一个锁,但是在锁定之后,会立即调用getter。调用者等待锁释放,然后获取新实例。
  3. 您没有在设置器上的锁定,并且在替换实例前 会出现调用。调用者获取旧实例。
  4. 您没有在设置器上的锁定,并且在替换实例后 会出现呼叫。调用者获取新实例。

无论有锁还是无锁,调用者接收哪个实例的时间都是一个问题。

我唯一看到的问题是您是否希望将Instance设置为null。在这种情况下,您的当前代码将不起作用,因为可以在_instance语句和返回语句之间更改if。您可以通过复制参考来解决此问题:

public static MyClass Instance
{
    get
    {
        var instanceSafeRef = _instance;
        if(instanceSafeRef == null)
        {
            lock(_padlock)
            {
                if(_instance == null)
                {
                    _instance = new MyClass();
                }
                instanceSafeRef = _instance;
            }
        }
        return instanceSafeRef;
    }
    set => _instance = value;
}