【问题标题】:When should I lock a static instance in multithreaded singletons when using a setter?使用 setter 时,何时应该在多线程单例中锁定静态实例?
【发布时间】:2019-03-27 02:31:52
【问题描述】:

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

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;
    }

}

【问题讨论】:

  • 为什么要有二传手?是否可以在某个时候将MyClass 替换为新的?
  • 正确。它将用作我正在实现工厂类的环境上下文模式的一部分。我看不出锁定它的理由,但它似乎也不是线程安全的。
  • 我建议将 setter 隐藏在一个单独的适当命名的方法后面,以阻止随机分配,因为可能会发生这种情况。也不,它不是线程安全的。
  • 我同意它不是线程安全的,但原子分配让我很困惑。感谢大家的帮助。

标签: c# multithreading thread-safety


【解决方案1】:

这称为双重检查锁定

但是,双重检查锁定要求底层字段是volatile1

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

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

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

注意:完全无人看管的 setter 进一步增加了复杂性3..

但是,在您的情况下,使用 双重检查锁定 可能会在单个检查和锁定 volatile 字段时正常工作,但我认为您最好的选择是完整的 @987654332 @一切都安全

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

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

注意:是的,它会导致性能损失


参考


其他资源

【讨论】:

  • 谢谢。我知道双重检查锁定并且更喜欢使用 Lazy ,但这是我需要设置器的罕见情况。过去我使用了 volatile 关键字,但使用了 int (例如)。所以你建议锁定设置器,使其成为线程安全的?
  • @ChrisGessler 是的,您还需要锁定 setter。
  • @MichaelRandall - 你能解释一下不锁定它的负面影响吗?例如,我会以损坏的内存告终吗?它会导致 exe 崩溃还是我只是收到对象的缓存(旧)副本?我尝试使用数千个线程使应用程序崩溃,但它不能。
  • @ChrisGessler 不,分配是原子的,最坏的情况是你最终得到了错误的引用,你认为你得到了默认值,但它是一个不同的默认实例化,或者你认为你已经设置了它, 但你最终会得到默认值,反之亦然
  • @MichaelRandall - 我同意。安全第一!感谢您的帮助!
【解决方案2】:

在我看来,无论是锁定还是不锁定(在设置器上),您总会遇到时间问题。想象一下这些场景:

  1. 您在 setter 上有一个锁,但在锁接合之前调用了一个 getter。调用者获取旧实例。
  2. 您在 setter 上有一个锁,但在锁被使用后立即调用了对 getter 的调用。调用者等待锁释放,然后获取新实例。
  3. 您没有对 setter 进行锁定,并且调用只是在您替换实例之前进入。调用者获取旧实例。
  4. 您没有对 setter 进行锁定,并且调用在您替换实例之后 进来。调用者获取新实例。

有锁和无锁,调用者接收哪个实例只是时间问题。

我能看到的唯一问题是您是否希望能够将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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-02
    • 2011-04-20
    • 2019-03-02
    • 1970-01-01
    相关资源
    最近更新 更多