为什么要使用双重锁定?

时间:2011-05-23 04:11:55

标签: c# java synchronization double-checked-locking compare-and-swap

我继续运行使用双重检查锁定的代码,我仍然对它为什么会被使用感到困惑。

我最初不知道double-checked locking is broken,当我了解它时,它为我放大了这个问题:为什么人们首先使用它?是不是比较和交换更好?

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

(我的问题同时适用于C#和Java,尽管上面的代码是针对C#的。)

与原子操作相比,双重检查锁定是否具有某种固有优势?

5 个答案:

答案 0 :(得分:12)

  

与原子操作相比,双重检查锁定是否具有某种固有优势?

(这个答案只涵盖C#;我不知道Java的内存模型是什么样的。)

主要区别在于潜在的种族。如果你有:

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

然后可以在不同的线程上任意多次调用FetchNewValue()。其中一个主题赢得了比赛。如果FetchNewValue()非常昂贵并且您想要确保它只被调用一次,那么:

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

保证FetchNewValue只被调用一次。

如果我个人想要进行低锁延迟初始化,那么我就按照你的建议行事:我使用互锁操作并忍受罕见的竞争条件,其中两个线程都运行初始化器并且只有一个获胜。如果这是不可接受的,那么我使用锁。

答案 1 :(得分:4)

在C#中,它从未被打破过,所以我们现在可以忽略它。

您发布的代码假定newValue已经可用,或者是(重新)计算。在双重检查锁定中,您可以保证只有一个线程实际执行初始化。

然而,在现代C#中,我通常更喜欢使用Lazy<T>来处理初始化。

答案 2 :(得分:1)

当锁定整个方法时遇到性能下降很严重时,使用双重检查锁定。换句话说,如果您不希望在对象(调用方法)或类上进行同步,则可以使用双重检查锁定。

如果存在很多争用锁,并且受锁保护的资源创建成本高,则可能出现这种情况;我们希望将创建过程推迟到需要之前。双重检查锁定通过首先验证条件(锁定提示)来帮助确定是否必须获得锁定来提高性能。

在Java 5中,当引入新的内存模型时,双重检查锁定在Java中被破坏。在那之前,锁定提示很可能在一个线程中为true,而在另一个线程中为false。在任何情况下,Initialization-on-Demand-Holder成语都是双重检查锁定模式的合适替代品;我发现这更容易理解。

答案 3 :(得分:0)

好吧,我认为唯一的优势是(幻觉)性能:以非线程安全的方式检查,然后执行一些锁定操作来检查变量,这可能是很贵。然而,由于双重检查的锁定方式被打破,从而排除了非线程安全检查的任何确定结论,并且它总是对我过早优化,我会声称不,没有优势 - 它是一个过时的预Java-days习语 - 但是很想得到纠正。

编辑:要清楚(呃),我相信双重检查锁定是一种习惯用法,每次都会在锁定和检查时发展为性能增强,而且大致上与非封装比较一样 - 和交换。我个人也是封装代码的同步部分的粉丝,所以我认为调用另一个操作来做脏工作更好。

答案 4 :(得分:0)

在某种程度上“有意义”,只有在启动时才会改变的值不需要被锁定才能被访问,但是你应该添加一些锁定(你可能根本不需要)如果两个线程在启动时尝试访问它,它大部分时间都可以工作。 它坏了,但我可以看出它为什么容易陷入陷阱。