简体   繁体   English

为什么要使用双重锁定?

[英]Why Double-Checked Locking is used at all?

I keep on running across code that uses double-checked locking, and I'm still confused as to why it's used at all. 我继续运行使用双重检查锁定的代码,我仍然对它为什么会被使用感到困惑。

I initially didn't know that double-checked locking is broken , and when I learned it, it magnified this question for me: why do people use it in the first place? 我最初不知道双重检查锁定是否被打破 ,当我学会它时,它为我放大了这个问题:为什么人们首先使用它? Isn't compare-and-swap better? 是不是比较和交换更好?

if (field == null)
    Interlocked.CompareExchange(ref field, newValue, null);
return field;

(My question applies to both C# and Java, although the code above is for C#.) (我的问题同时适用于C#和Java,尽管上面的代码是针对C#的。)

Does double-checked locking have some sort of inherent advantage compared to atomic operations? 与原子操作相比,双重检查锁定是否具有某种固有优势?

Does double-checked locking have some sort of inherent advantage compared to atomic operations? 与原子操作相比,双重检查锁定是否具有某种固有优势?

(This answer only covers C#; I have no idea what Java's memory model is like.) (这个答案只涉及C#;我不知道Java的内存模型是什么样的。)

The principle difference is the potential race. 主要区别在于潜在的种族。 If you have: 如果你有:

if (f == null)
    CompareExchange(ref f, FetchNewValue(), null)

then FetchNewValue() can be called arbitrarily many times on different threads. 然后可以在不同的线程上任意多次调用FetchNewValue()。 One of those threads wins the race. 其中一个主题赢得了比赛。 If FetchNewValue() is extremely expensive and you want to ensure that it is called only once, then: 如果FetchNewValue()非常昂贵,并且您希望确保仅调用一次,那么:

if (f == null)
    lock(whatever)
        if (f == null)
            f = FetchNewValue();

Guarantees that FetchNewValue is only called once. 保证FetchNewValue只被调用一次。

If I personally want to do a low-lock lazy initialization then I do what you suggest: I use an interlocked operation and live with the rare race condition where two threads both run the initializer and only one wins. 如果我个人想要进行低锁懒惰初始化,那么我按照你的建议行事:我使用一个互锁操作并忍受罕见的竞争条件,其中两个线程都运行初始化器,只有一个获胜。 If that's not acceptable then I use locks. 如果这是不可接受的,那么我使用锁。

In C#, it's never been broken, so we can ignore that for now. 在C#中,它从未被打破过,所以我们现在可以忽略它。

The code you've posted assumes that newValue is already available, or is cheep to (re-) calculate. 您发布的代码假定newValue已经可用,或者是(重新)计算。 In double-checked locking, you're guaranteed that only one thread will actually perform the initialization. 在双重检查锁定中,您可以保证只有一个线程实际执行初始化。

That being said, however, in modern C#, I'd normally prefer to just use a Lazy<T> to deal with the initialization. 尽管如此,在现代C#中,我通常更喜欢使用Lazy<T>来处理初始化。

Double-checked locking is used when the performance degradation encountered when locking on the entire method is significant. 当锁定整个方法时遇到的性能下降很严重时,使用双重检查锁定。 In other words, if you do not wish to synchronize on the object (on which the method is invoked) or the class, you may use double-checked locking. 换句话说,如果您不希望在对象(调用方法)或类上进行同步,则可以使用双重检查锁定。

This may be the case if there is a lot of contention for the lock and when the resource protected by the lock is expensive to create; 如果存在很多争用锁并且由锁保护的资源创建成本高,则可能是这种情况; one would like to defer the creation process until it is required. 我们希望将创建过程推迟到需要之前。 Double checked locking improves performance by first verifying a condition (lock hint) to aid in determining whether the lock must be obtained. 双重检查锁定通过首先验证条件(锁定提示)来帮助确定是否必须获得锁定来提高性能。

Double checked locking was broken in Java until Java 5, when the new memory model was introduced. 在Java 5中,当引入新的内存模型时,双重检查锁定在Java中被破坏。 Until then, it was quite possible for the lock hint to be true in one thread, and false in another. 在那之前,锁定提示很可能在一个线程中为true,而在另一个线程中为false。 In any case, the Initialization-on-Demand-Holder idiom is a suitable replacement for the double-checked locking pattern; 在任何情况下, Initialization-on-Demand-Holder惯用法都是双重检查锁定模式的合适替代品; I find this much easier to understand. 我发现这更容易理解。

Well, the only advantage that comes to my mind is (the illusion of) performance: check in a non-thread-safe way, then do some locking operations to check the variable, which may be expensive. 好吧,我想到的唯一优势是(幻觉)性能:以非线程安全的方式检查,然后执行一些锁定操作来检查变量,这可能很昂贵。 However, since double checked locking is broken in a way that precludes any firm conclusions from the non-thread-safe check, and it always smacked of premature optimization to me anyway, I would claim no, no advantage - it is an outdated pre-Java-days idiom - but would love to be corrected. 然而,由于双重检查的锁定方式被打破,从而排除了非线程安全检查的任何确定结论,并且它总是对我过早优化,我会声称不,没有优势 - 它是一个过时的预Java天成语 - 但很想得到纠正。

Edit: to be clear(er), I believe double checked locking is an idiom that evolved as a performance enhancement on locking and checking every time, and, roughly, is close to the same thing as a non-encapsulated compare-and-swap. 编辑:要清楚(呃),我相信双重检查锁定是一种习惯用法,每次都会在锁定和检查时发展为性能增强,并且大致与非封装的比较和交换接近相同的东西。 I'm personally also a fan of encapsulating synchronized sections of code, though, so I think calling another operation to do the dirty work is better. 我个人也是封装代码的同步部分的粉丝,所以我认为调用另一个操作来做脏工作更好。

It "make sense" on some level that a value that would only change at startup shouldn't need to be locked to be accessed, but then you should add some locking (which you probably aren't going to need) just in case two threads try to access it at start up and it works most of the time. 在某种程度上“有意义”的是,只有在启动时才能更改的值不需要被锁定才能被访问,但是你应该添加一些锁定(你可能根本不需要),以防万一线程尝试在启动时访问它,它大部分时间都可以工作。 Its broken, but I can see why its an easy trap to fall in to. 它坏了,但我可以看出为什么它容易陷入困境。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM