6

我一直在运行使用双重检查锁定的代码,但我仍然对为什么要使用它感到困惑。

我一开始并不知道双重检查锁定是坏的,当我学会它时,它对我来说放大了这个问题:为什么人们首先要使用它?比较和交换不是更好吗?

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

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

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

4

5 回答 5

12

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

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

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

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

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

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

保证 FetchNewValue 只被调用一次。

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

于 2011-05-23T15:44:00.360 回答
4

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

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

话虽这么说,但是,在现代 C# 中,我通常更喜欢只使用 aLazy<T>来处理初始化。

于 2011-05-23T04:27:09.573 回答
1

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

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

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

于 2011-05-23T04:33:09.213 回答
0

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

编辑:要清楚(呃),我相信双重检查锁定是一个习惯用法,它演变为每次锁定和检查的性能增强,并且大致与非封装比较和交换相同. 不过,我个人也喜欢封装同步的代码部分,所以我认为调用另一个操作来完成脏活会更好。

于 2011-05-23T04:16:17.940 回答
0

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

于 2011-05-23T04:25:49.397 回答