【问题标题】:Issue with Double Check Locking in Java [duplicate]Java中的双重检查锁定问题[重复]
【发布时间】:2019-04-04 06:13:28
【问题描述】:

其中一篇文章提到了"Double Check Locking" 的问题。请看下面的例子

public class MyBrokenFactory {
  private static MyBrokenFactory instance;
  private int field1, field2 ...

  public static MyBrokenFactory getFactory() {
    // This is incorrect: don't do it!
    if (instance == null) {
      synchronized (MyBrokenFactory.class) {
        if (instance == null)
          instance = new MyBrokenFactory();
      }
    }
    return instance;
  }

  private MyBrokenFactory() {
    field1 = ...
    field2 = ...
  }
}

原因:-(请注意编号的执行顺序)

线程 1:“首先进入”并开始创建实例。 1. 实例是否为空?是的。 2.同步上课。 3. 例如分配内存。 4. 指向保存到实例中的内存的指针。 [[线程 2]] 7. field1 和 field2 的值被写入 分配给对象的内存。 ..................... 线程 2:在线程 1 写入对象引用时进入 到内存,但在它写完所有字段之前。 5. 实例是否为空?不。 6.instance不为null,但是field1和field2还没有设置! 此线程看到 field1 和 field2 的无效值!

问题:
由于新实例的创建(new MyBrokenFactory())是从同步块中完成的,在整个初始化完成之前会不会释放锁(私有的MyBrokenFactory()完全执行)?

参考 - https://www.javamex.com/tutorials/double_checked_locking.shtml

请解释一下。

【问题讨论】:

  • 另外简短地回答一下您的规范“将在整个初始化完成之前释放锁”答案是:不。但由于第二个线程甚至从未尝试获取锁,这在这里对您没有帮助。
  • 我想解决办法是声明instancevolatile
  • 我知道 volatile 会修复它。伙计们,我不是在问什么是修复我的问题是关于线程锁定的基本问题。由于新实例的创建(new MyBrokenFactory())是从同步块中完成的,在整个初始化完成之前会释放锁吗(私有的MyBrokenFactory()完全执行)?
  • @Sunny 问题是不能定义“整个初始化完成”。是的,对于创建它的线程,答案是肯定的。但是其他线程可能会观察到部分构造的对象,因为缺少同步
  • 更准确地说,如果第二个线程观察到instance 的非空值,则您无法可靠地访问它,因为该线程没有进入同步块,因此对象未同步跨度>

标签: java multithreading synchronization thread-safety


【解决方案1】:

问题出在这里:

线程 2:在线程 1 将对象引用写入内存时进入,但在它写入所有字段之前。

实例是否为空?没有。

如果没有同步,线程 2 可能看到 instancenull,即使线程 1 已经编写了它。请注意,instance 的第一次检查位于 synchronized 块之外:

if (instance == null) {
  synchronized (MyBrokenFactory.class) {

由于第一次检查是在块外完成的,因此无法保证线程 2 会看到正确的 instance 值。

我不知道你想用 field1field2 做什么,你甚至从来没有写过它们。

回复。您的编辑:

由于新实例(new MyBrokenFactory())的创建是从同步块完成的

我认为您要问的是,field1field2 这两个实例字段是否保证可见。答案是否定的,问题与instance 相同。因为您没有从同步块中读取instance,所以无法保证这些实例字段将被正确读取。如果instance 不为空,则永远不会进入synchronized 块,因此不会发生同步。

【讨论】:

  • 我知道 volatile 会修复它。伙计们,我不是在问什么是修复我的问题是关于线程锁定的基本问题。由于新实例的创建(new MyBrokenFactory())是从同步块中完成的,在整个初始化完成之前会释放锁吗(私有的MyBrokenFactory()完全执行)?
  • 这有什么关系?你为什么在乎?代码坏了,什么时候释放锁其实并不重要。
【解决方案2】:

请找到我的问题的答案。我通过查看another similar question here得到了答案。

同步保证,只有一个线程可以进入一段代码。但它不能保证在同步部分中完成的变量修改对其他线程是可见的。只有进入同步块的线程才能保证看到更改。这就是双重检查锁定被破坏的原因 - 它在读者方面不同步。读取线程可能会看到,单例不为空,但单例数据可能未完全初始化(可见)。

订购由volatile 提供。 volatile 保证排序,例如写入易失性单例静态字段保证写入单例对象将在写入易失性静态字段之前完成。它不会阻止创建两个对象的单例,这是由 synchronize 提供的。

类最终静态字段不需要是易失的。在 Java 中,JVM 处理了这个问题。

【讨论】:

  • 这里有一个更正:静态字段的初始化不需要进一步同步(当作为该类中的初始化程序或静态块完成时)。但是,初始化后的每次写入都需要以某种方式同步。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多