Java中同步的可见性影响

这篇文章说:


在这个不合规的代码示例中,Helper 类通过将其字段声明为 final 使其不可变。JMM 保证不可变对象在它们对任何其他线程可见之前被完全构造。getHelper() 方法中的块同步保证所有可以看到 helper 字段的非空值的线程也将看到完全初始化的 Helper 对象。


public final class Helper {

  private final int n;


  public Helper(int n) {

    this.n = n;

  }


  // Other fields and methods, all fields are final

}


final class Foo {

  private Helper helper = null;


  public Helper getHelper() {

    if (helper == null) {            // First read of helper

      synchronized (this) {

        if (helper == null) {        // Second read of helper

          helper = new Helper(42);

        }

      }

    }


    return helper;                   // Third read of helper

  }

}

但是,此代码不能保证在所有 Java 虚拟机平台上都能成功,因为在 helper 的第一次读取和第三次读取之间没有发生之前的关系。因此,第三次读取 helper 有可能获得一个陈旧的空值(可能是因为它的值被编译器缓存或重新排序),导致 getHelper() 方法返回一个空指针。


我不知道该怎么办。我同意一读和三读之间没有发生之前的关系,至少没有直接的关系。从某种意义上说,第一次读取必须在第二次之前发生,第二次读取必须在第三次之前发生,因此第一次读取必须在第三次之前发生时,是否存在传递性发生在之前的关系?


有人能更专业地阐述吗?


智慧大石
浏览 195回答 3
3回答

慕尼黑5688855

不,没有传递关系。JMM 背后的想法是定义 JVM 必须遵守的规则。如果 JVM 遵循这些规则,它们就有权根据需要重新排序和执行代码。在您的示例中,第 2 次读取和第 3 次读取不相关 -例如使用synchronized或不会引入内存障碍volatile。因此,JVM 可以按如下方式执行它: public Helper getHelper() {    final Helper toReturn = helper;  // "3rd" read, reading null    if (helper == null) {            // First read of helper      synchronized (this) {        if (helper == null) {        // Second read of helper          helper = new Helper(42);        }      }    }    return toReturn; // Returning null  }然后您的调用将返回一个空值。然而,会创建一个单例值。但是,后续调用可能仍会获得空值。正如所建议的,使用 volatile 会引入新的内存屏障。另一种常见的解决方案是捕获读取的值并返回它。 public Helper getHelper() {    Helper singleton = helper;    if (singleton == null) {      synchronized (this) {        singleton = helper;        if (singleton == null) {          singleton = new Helper(42);          helper = singleton;        }      }    }    return singleton;  }由于您依赖于局部变量,因此无需重新排序。一切都发生在同一个线程中。

www说

不,这些读取之间没有任何传递关系。synchornized只保证在同一个锁的同步块内所做的更改的可见性。在这种情况下,所有读取都不会使用同一个锁上的同步块,因此这是有缺陷的并且不保证可见性。因为一旦字段被初始化就没有锁定,所以声明该字段是至关重要的volatile。这将确保可见性。private volatile Helper helper = null;

小怪兽爱吃肉

这一切都在这里解释https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories,问题很简单。... 请注意,我们在此代码中对实例进行了多次读取,并且至少“读取 1”和“读取 3”是没有任何同步的读取...规范方面,如发生在一致性规则中所述,读取操作可以通过竞态观察无序写入。这是为每个读取操作决定的,无论其他哪些操作已经读取了相同的位置。在我们的例子中,这意味着即使“read 1”可以读取非空实例,代码然后继续返回它,然后它进行另一个活泼的读取,它可以读取一个空实例,它将被返回!
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java