Java中同步的可见性影响

时间:2018-10-14 17:00:12

标签: java multithreading thread-safety visibility

This文章说:

  

在此不兼容的代码示例中,Helper类变为不可变的   通过最终声明其字段。 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 Virtual上都能成功   机器平台,因为没有事前发生的关系   在辅助程序的第一读和第三读之间。因此,这是   助手的三次读取有可能获得过时的null值   (可能是因为其值已由编译器缓存或重新排序),   导致getHelper()方法返回空指针。

我不知道该怎么做。我可以同意,在第一读和第三读之间没有任何关系,至少没有立即关系。在某种意义上说,第一读必须在第二之前发生,并且第二读必须在第三之前发生,所以第一读必须在第三之前发生

有人能更熟练地进行阐述吗?

3 个答案:

答案 0 :(得分:3)

不,没有传递关系。

JMM背后的想法是定义JVM必须遵守的规则。只要JVM遵循这些规则,就可以根据需要授权它们重新排序和执行代码。

在您的示例中,第2次读取和第3次读取不相关-例如,使用synchronizedvolatile不会引入存储障碍。因此,允许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;
  }

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

答案 1 :(得分:2)

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

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

private volatile Helper helper = null;

答案 2 :(得分:0)

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

  

...请注意,我们在这段代码中以及在   至少“ read 1”和“ read 3”是没有任何读数的读数   同步......规范规范,如之前所述   一致性规则,读取动作可以通过观察无序写入   比赛。这是针对每个读取操作决定的,无论其他操作如何   操作已读取相同的位置。在我们的示例中   表示即使“ read 1”可以读取非null实例,该代码   然后继续归还它,然后再读一遍,   可以读取一个空实例,该实例将被返回!