Java中双重检查锁存在问题

3

其中一篇文章提到了“双重检查锁定”(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. 实例非空,但尚未设置field1和field2! 此线程看到的是field1和field2的无效值!
问题:
由于新实例(new MyBrokenFactory())的创建是从同步块中完成的,因此在整个初始化完成之前(private MyBrokenFactory()完全执行)是否会释放锁定? 参考资料 - https://www.javamex.com/tutorials/double_checked_locking.shtml 请解释。

同时,简短回答一下关于您的规格说明“在整个初始化完成之前是否会释放锁定”的问题:答案是:不会。但由于第二个线程甚至没有尝试获取锁定,因此这对您没有帮助。 - Ben
1
我知道使用volatile可以解决这个问题。伙计们,我不是在问如何解决这个问题,我的问题是关于线程锁的基本问题。由于新实例(new MyBrokenFactory())的创建是在同步块中完成的,那么在整个初始化完成之前(即private MyBrokenFactory()完全执行之前),锁是否会被释放? - Sunny
1
@Sunny,问题在于你无法定义“整个初始化过程已完成”。是的,对于创建它的线程来说,答案是肯定的。但其他线程可能会观察到一个部分构造的对象,因为缺少同步。 - Tootsie
@Tootsie:这正是我正在寻找的。谢谢,我也从 SO 上的另一个问题中获得了这些信息。 - Sunny
1
更准确地说,如果第二个线程观察到instance的非空值,则您无法可靠地访问它,因为该线程不进入同步块,因此对象未同步。 - Tootsie
显示剩余2条评论
2个回答

0
请帮我找到问题的答案。我通过查看这里的另一个类似问题得到了答案。

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

顺序由volatile提供。例如,写入volatile单例静态字段保证在写入volatile静态字段之前完成对单例对象的写入。它不能防止创建两个对象的单例,这由同步提供。

类final静态字段不需要是volatile。在Java中,JVM会解决这个问题。


这里需要更正一下:静态字段的初始化不需要进一步同步(当作为该类中的初始化程序或静态块时)。然而,初始化之后的每次写入都需要以某种方式进行同步。 - markspace

0

问题在这里:

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

实例是否为空?不是。

如果没有同步,线程2可能会将instance视为null,即使线程1已经写入它。请注意,第一次检查instancesynchronized块之外:

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

由于第一个检查是在块外完成的,因此不能保证线程2将看到instance的正确值。

我不知道您想要使用field1field2做什么,您甚至从未编写过它们。

关于您的编辑:

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

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


我知道使用volatile可以解决这个问题。伙计们,我不是在问如何解决这个问题,我的问题是关于线程锁的基本问题。由于新实例(new MyBrokenFactory())的创建是在同步块中完成的,那么在整个初始化完成之前(即private MyBrokenFactory()完全执行之前),锁是否会被释放? - Sunny
这跟什么有关系呢?你为什么关心这个?代码出了问题,锁何时释放实际上并不重要。 - markspace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接