为了账号安全,请及时绑定邮箱和手机立即绑定

Java中同步的可见性影响

Java中同步的可见性影响

智慧大石 2021-11-17 15:29:59
这篇文章说:在这个不合规的代码示例中,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() 方法返回一个空指针。我不知道该怎么办。我同意一读和三读之间没有发生之前的关系,至少没有直接的关系。从某种意义上说,第一次读取必须在第二次之前发生,第二次读取必须在第三次之前发生,因此第一次读取必须在第三次之前发生时,是否存在传递性发生在之前的关系?有人能更专业地阐述吗?
查看完整描述

3 回答

?
慕尼黑5688855

TA贡献1848条经验 获得超2个赞

不,没有传递关系。


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;

  }

由于您依赖于局部变量,因此无需重新排序。一切都发生在同一个线程中。


查看完整回答
反对 回复 2021-11-17
?
www说

TA贡献1775条经验 获得超8个赞

不,这些读取之间没有任何传递关系。synchornized只保证在同一个锁的同步块内所做的更改的可见性。在这种情况下,所有读取都不会使用同一个锁上的同步块,因此这是有缺陷的并且不保证可见性。


因为一旦字段被初始化就没有锁定,所以声明该字段是至关重要的volatile。这将确保可见性。


private volatile Helper helper = null;


查看完整回答
反对 回复 2021-11-17
?
小怪兽爱吃肉

TA贡献1852条经验 获得超1个赞

这一切都在这里解释https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories,问题很简单。

... 请注意,我们在此代码中对实例进行了多次读取,并且至少“读取 1”和“读取 3”是没有任何同步的读取...规范方面,如发生在一致性规则中所述,读取操作可以通过竞态观察无序写入。这是为每个读取操作决定的,无论其他哪些操作已经读取了相同的位置。在我们的例子中,这意味着即使“read 1”可以读取非空实例,代码然后继续返回它,然后它进行另一个活泼的读取,它可以读取一个空实例,它将被返回!


查看完整回答
反对 回复 2021-11-17
  • 3 回答
  • 0 关注
  • 194 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信