简体   繁体   English

Java中同步的可见性影响

[英]Visibility effects of synchronization in Java

This article says: 这篇文章说:

In this noncompliant code example, the Helper class is made immutable by declaring its fields final. 在这个不兼容的代码示例中,通过将其字段声明为final来使Helper类不可变。 The JMM guarantees that immutable objects are fully constructed before they become visible to any other thread. JMM保证不可变对象在任何其他线程可见之前就已完全构建。 The block synchronization in the getHelper() method guarantees that all threads that can see a non-null value of the helper field will also see the fully initialized Helper object. 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
  }
}

However, this code is not guaranteed to succeed on all Java Virtual Machine platforms because there is no happens-before relationship between the first read and third read of helper. 但是,不能保证此代码在所有Java虚拟机平台上都能成功,因为在辅助程序的第一次读取和第三次读取之间没有之前发生的关系。 Consequently, it is possible for the third read of helper to obtain a stale null value (perhaps because its value was cached or reordered by the compiler), causing the getHelper() method to return a null pointer. 因此,对辅助程序的第三次读取有可能获得陈旧的空值(可能是因为其值已由编译器缓存或重新排序),从而导致getHelper()方法返回空指针。

I don't know what to make of it. 我不知道该怎么做。 I can agree that there is no happens before relationship between first and third read, at least no immediate relationship. 我可以同意,在第一读和第三读之间没有任何关系,至少没有直接的关系。 Isn't there a transitive happens-before relationship in a sense that first read must happen before second, and that second read has to happen before third, therefore first read has to happen before third 在某种意义上说,第一读必须在第二之前发生,并且第二读必须在第三之前发生,因此第一读必须在第三之前发生

Could someone elaborate more proficiently? 有人可以更熟练地进行详细说明吗?

No, there is no transitive relationship. 不,没有传递关系。

The idea behind the JMM is to define rules that JVM must respect. JMM背后的想法是定义JVM必须遵守的规则。 Providing the JVM follows these rules, they are authorized to reorder and execute code as they want. 只要JVM遵循这些规则,就可以根据需要授权它们重新排序和执行代码。

In your example, the 2nd read and the 3rd read are not related - no memory barrier introduced by the use of synchronized or volatile for example. 在您的示例中,第2次读取和第3次读取不相关-例如,通过使用synchronizedvolatile不会引入任何存储障碍。 Thus, the JVM is allowed to execute it as follow: 因此,允许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
  }

Your call would then return a null value. 然后,您的呼叫将返回空值。 Yet, a singleton value would have been created. 但是,将创建一个单例值。 However, sub-sequent calls may still get a null value. 但是,后续调用仍可能会得到一个空值。

As suggested, using a volatile would introduce new memory barrier. 如建议的那样,使用易失性会引入新的存储障碍。 Another common solution is to capture the read value and return it. 另一个常见的解决方案是捕获读取的值并将其返回。

 public Helper getHelper() {
    Helper singleton = helper;
    if (singleton == null) {
      synchronized (this) {
        singleton = helper;
        if (singleton == null) {
          singleton = new Helper(42);
          helper = singleton;
        }
      }
    }

    return singleton;
  }

As your rely on a local variable, there is nothing to reorder. 由于您依赖局部变量,因此无需重新排序。 Everything is happening in the same thread. 一切都在同一线程中发生。

No, there's no any transitive relationship between those reads. 不,这些读段之间没有任何传递关系。 synchornized only guarantees visibility of changes that were made within synchronized blocks of the same lock. synchornized仅保证可见同一锁的同步块中所做的更改。 In this case all reads do not use the synchronized blocks on the same lock, hence this is flawed and visibility is not guaranteed. 在这种情况下,所有读取都不会在同一锁上使用同步块,因此存在缺陷,并且无法保证可见性。

Because there is no locking once the field is initialized, it is critical that the field be declared volatile . 因为一旦字段初始化就没有锁定,所以将字段声明为volatile至关重要。 This will ensure the visibility. 这样可以确保可见性。

private volatile Helper helper = null;

It's all explained here https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories , the issue simple. 一切在这里https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories进行了解释,这个问题很简单。

... Notice that we do several reads of instance in this code, and at least "read 1" and "read 3" are the reads without any synchronization ... Specification-wise, as mentioned in happens-before consistency rules, a read action can observe the unordered write via the race. ...请注意,我们在这段代码中对实例进行了多次读取,并且至少“ read 1”和“ read 3”是没有任何同步的读取...在规范方面,如发生在一致性规则之前的那样,读取操作可以通过种族观察无序写入。 This is decided for each read action, regardless what other actions have already read the same location. 这是针对每个读取动作决定的,而不管其他什么动作已经读取同一位置。 In our example, that means that even though "read 1" could read non-null instance, the code then moves on to returning it, then it does another racy read, and it can read a null instance, which would be returned! 在我们的示例中,这意味着即使“读取1”可以读取非null实例,但代码随后继续返回它,然后又读取了一个原始值,并且可以读取将返回的null实例!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM