首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Java中同步的可见性效果

Java中同步的可见性效果
EN

Stack Overflow用户
提问于 2018-10-15 01:00:12
回答 3查看 169关注 0票数 1

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

代码语言:javascript
复制
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
  }
}

然而,这段代码并不能保证在所有

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

我不知道该怎么理解它。我同意在第一次读和第三次读之间没有关系发生,至少没有immediate关系。从某种意义上说,第一次读取必须发生在第二次读取之前,第二次读取必须发生在第三次读取之前,因此第一次读取必须发生在第三次读取之前,这是否存在可传递的发生之前关系

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-10-15 02:14:33

不,没有传递关系。

JMM背后的思想是定义JVM必须遵守的规则。只要JVM遵循这些规则,它们就有权按自己的意愿重新排序和执行代码。

在您的示例中,第二次读取和第三次读取是不相关的-例如,使用synchronizedvolatile没有引入内存障碍。因此,允许JVM执行它,如下所示:

代码语言:javascript
复制
 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
  }

然后,您的调用将返回空值。然而,已经创建了一个单例的值。但是,后续调用仍可能得到空值。

正如所建议的,使用易失性会引入新的内存障碍。另一种常见的解决方案是捕获读取值并返回它。

代码语言:javascript
复制
 public Helper getHelper() {
    Helper singleton = helper;
    if (singleton == null) {
      synchronized (this) {
        singleton = helper;
        if (singleton == null) {
          singleton = new Helper(42);
          helper = singleton;
        }
      }
    }

    return singleton;
  }

因为您依赖于局部变量,所以没有什么需要重新排序的。所有的事情都发生在同一个线程中。

票数 3
EN

Stack Overflow用户

发布于 2018-10-15 01:06:27

在这种情况下,所有读操作都不使用同一锁上的同步块,因此这是有缺陷的,并且可见性不能得到保证。

由于字段初始化后不会锁定,因此将字段声明为volatile非常重要。这将确保可见性。

代码语言:javascript
复制
private volatile Helper helper = null;
票数 2
EN

Stack Overflow用户

发布于 2018-10-15 02:11:12

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

...注意,我们在这段代码中对实例进行了多次读取,至少“Read1”和“Read3”是没有任何同步 的读取...就规范而言,正如在一致性规则之前发生的那样,读取操作可以通过竞态观察无序写入。这是针对每个读取操作而决定的,而不管哪些其他操作已经读取了相同的位置。在我们的示例中,这意味着即使“Read1”可以读取非空实例,代码也会继续返回它,然后它会进行另一次快速读取,并且它可以读取将返回的空实例!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52805020

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档