为什么要使用双重锁定?

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

我最初不知道双重检查锁定是否被打破 ,当我学会它时,它为我放大了这个问题:为什么人们首先使用它? 是不是比较和交换更好?

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

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

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

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

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

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

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

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

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

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

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

保证FetchNewValue只被调用一次。

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

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

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

尽管如此,在现代C#中,我通常更喜欢使用Lazy来处理初始化。

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

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

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

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