【问题标题】:is "Double-Checked Locking is Broken" a java-only thing?“双重检查锁定被破坏”是仅限java的东西吗?
【发布时间】:2011-08-22 23:02:00
【问题描述】:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 的页面说双重检查锁定在 java 中是有缺陷的。我只是想知道它是否也适用于其他语言(C#、Vb、C++ 等)

我读过Double checked locking pattern: Broken or not?Is this broken double checked locking?How to solve the "Double-Checked Locking is Broken" Declaration in Java? 说实话,我不知道共同的共识是什么。有人说是,它坏了,其他人说不是。

无论如何,我的问题是它是否也适用于其他语言(C#、Vb、C++ 等)

【问题讨论】:

  • 根据我读过的专家意见,只要使用volatile,它在 C# 中应该是安全的。同样,我相信较新版本的 java 之一使其安全。我对此不是很自信,也没有必要的其他语言专业知识,所以我不愿意发布答案。
  • 考虑到你有 java >= 1.5 并应用正确的模式并且 Joshua Bloch 对你有足够的权限,双重检查没有被破坏:)
  • 是的,在 Java >= 1.5 中,使用 volatile,它可以工作。但这并不一定意味着你应该使用它:ibm.com/developerworks/library/j-jtp03304
  • 一般来说,这取决于您的锁定和可见性语义,即取决于您的语言实现的内存模型(或您的多线程/锁定库)。
  • @T.J.克劳德 - 在真正需要之前,你绝对应该避免它,我同意这一点。但它本身并没有损坏..

标签: c# java vb.net multithreading synchronization


【解决方案1】:

双重检查锁定在 Java 中是安全的,前提是:

  1. 实例变量被声明为volatile,并且
  2. JVM 正确实现了 JSR-133 规范;即它与 Java 5 及更高版本兼容。

我的来源是JSR-133 (Java Memory Model) FAQ - Jeremy Manson and Brian Goetz, February 2004。 Goetz 在其他许多地方都证实了这一点。

但是,正如 Goetz 所说,这是一个已经过时的成语。 Java 中的无争用同步现在很快,所以他建议如果您需要进行延迟初始化,您只需将getInstance() 方法声明为synchronized。 (我想这也适用于其他语言......)

此外,在所有条件相同的情况下,编写可在 Java 5 中运行但在旧版 JVM 中不可靠的代码是个坏主意。


好的,那么其他语言呢?嗯,这取决于 如何 成语是如何实现的,而且通常是在平台上。

  • C# - 根据https://stackoverflow.com/a/1964832/139985,实例变量需要是否可变取决于平台。但是,Wikipedia 表示,如果您确实使用volatile 或显式内存屏障,则可以安全地实现该习语。

  • VB - 根据Wikipedia,可以使用显式内存屏障安全地实现该习语。

  • C++ - 根据Wikipedia,在Visual C++ 2005 中使用volatile 可以安全地实现该习语。但其他消息来源说一般 C++ 语言规范不提供为volatile 提供足够的保证。然而,双重检查锁定可以在 C++ 2011 语言修订版的上下文中实现 - https://stackoverflow.com/a/6099828/139985

(注意:我只是总结了一些我发现的一些资源,这些资源在我看来是最近的......而且很合理。我不是 C++、C# 或 VB 专家。请阅读链接页面并做出自己的判断。 )

【讨论】:

  • 问题问 “无论如何,我的问题是它是否也适用于其他语言(C#、Vb、C++ 等)” 不是 Java。我们在 cmets 中介绍了 Java。
  • @T.J. - 我知道,但这个问题也传播了错误信息,应该在正确的答案中得到纠正。注释没有提供足够的空间来执行此操作。
  • final 关键字还可以确保在读取对象之前对其进行完全初始化。
  • 是的,但问题是关于 DCL。单例对象的初始化方式是正交的。
【解决方案2】:

这篇维基百科文章涵盖了 java、c++ 和 .net (c#/vb) http://en.wikipedia.org/wiki/Double-checked_locking

【讨论】:

  • 我查看了wikipedia article wrt C#,我认为使用内存屏障的版本存在缺陷。具体来说,如果一个线程在分配mySingleton 和屏障之间被抢占,那么第二个线程是否可能会看到MySingleton 的实例,该实例的字段由于out-而未被刷新顺序内存写入?
  • 维基百科文章的另一个问题(作为对这个问题的回答)是它只讨论了 Visual C++……而不是一般的 C++。
  • 以下粗体字究竟是什么意思:“C# 关键字 volatile 可用于围绕 mySingleton 的所有访问强制执行读/写栅栏,这将 否定许多效率 双重检查锁定策略所固有的。”
  • @Stephen C 你碰巧知道它们中粗体字的确切含义:“C# 关键字 volatile 可用于围绕 mySingleton 的所有访问强制执行读/写栅栏,这将 否定了双重检查锁定策略固有的许多效率。”
  • @Pacerier:维基百科的文章似乎假设将字段标记为 volatile 会由于栅栏指令而产生额外的开销。但是,在 x86/x64 上,围栏指令不是实现 volatile 所必需的(实际上不会由 JIT 发出)。在非 x86 架构(例如,Itanium)上,易失性字段确实有额外的成本。
【解决方案3】:

这是一个棘手的问题,存在着相互矛盾的信息的雷区。

部分问题在于双重检查锁定有几种变体:

  • 在快速路径上检查的字段可能不稳定。
  • 双重检查锁定有单字段变体和双字段变体。

不仅如此,不同的作者对模式“正确”的含义有不同的定义。

  • 定义 #1:被广泛接受的编程语言规范(例如 C# 的 ECMA)保证模式是正确的。
  • 定义 #2:该模式在实践中适用于特定架构(通常是 x86)。

尽管看起来令人不快,但很多代码都依赖于定义 #2。

我们以 C# 为例。在 C# 中,当且仅当字段是 volatile 时,根据定义 #1,双重检查模式(通常实现)是正确的。但是,如果我们考虑定义 #2,几乎所有变体在 X86 上都是正确的(即,碰巧工作),即使该字段是非易失性的。在 Itanium 上,如果字段是非易失性的,则单字段变体恰好可以工作,但双字段变体则不行。

不幸的结果是你会发现文章对这种模式的正确性做出了明显矛盾的陈述。

【讨论】:

    【解决方案4】:

    正如其他人所说,这个成语已经过时了。 FWIW,对于延迟初始化,.Net 现在提供了一个内置类:System.Lazy<T>(msdn)。不知道java中是否有类似的东西。

    【讨论】:

    • 据我所知,java中没有一个
    【解决方案5】:

    它在 Java 中存在缺陷,在 Java 5 中已得到修复。被破坏的事实更多是实现问题加上误解,而不是技术上的“坏主意”。

    【讨论】:

    • 其实我的问题是它是否也适用于其他语言(C#、Vb、C++ 等)
    • 实际上,如果您不想要 Java 相关的答案,您不应该用 'java' 标记问题。
    • 我知道,但是糟糕的实现可能会蔓延到任何语言中。其他语言也有实现错误的风险,但即使 C#、Vb、C++ 等有错误的实现,也很难将其称为“相同”错误。我的意思是,其中一些项目甚至不使用虚拟机!
    • Umm ... 在 Java 5 及更高版本中,仅当您将实例变量声明为 volatile 时,DCL 才是“固定”的。 JSR-133 使 DCL 成语发挥作用的作用是约束 volatile 的规范。
    • 嗯...在 Java 1 及更高版本中,只有在使用不可变数据结构和 final 变量时,常量才是“固定的”。无论是否有 JSR,都有正确的方法和一百万种错误的方法。问题是,由于误解加上实施问题,有一段时间没有正确的方法来做这件事,现在已经解决了(如果你做了“正确”的事情)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多