在 JMM 下读取对对象的引用和读取对象的字段

reading a reference to an object and reading the object’s fields under JMM

阅读后提出post:https://shipilev.net/blog/2016/close-encounters-of-jmm-kind/#pitfall-semi-sync

class Box {
  int x;
  public Box(int v) {
    x = v;
  }
}

class RacyBoxy {
  Box box;

  public synchronized void set(Box v) {
    box = v;
  }

  public Box get() {
    return box;
  }
}

并测试:

@JCStressTest
@State
public class SynchronizedPublish {
  RacyBoxy boxie = new RacyBoxy();

  @Actor
  void actor() {
    boxie.set(new Box(42)); // set is synchronized
  }

  @Actor
  void observer(IntResult1 r) {
    Box t = boxie.get(); // get is not synchronized
    if (t != null) {
      r.r1 = t.x;
    } else {
      r.r1 = -1;
    }
  }
}

作者说有可能r.r1 == 0。我同意 那。但是,我对解释感到困惑:

The actual failure comes from the fact that reading a reference to an object and reading the object’s fields are distinct under the memory model.

我同意

reading a reference to an object and reading the object’s fields are distinct under the memory model but, I don't see how it has an influence on result.

请帮我理解一下。

P.S。如果有人对 @Actor 感到困惑。它只是意味着:运行 在一个线程中。

我认为它解决了人们阅读代码时关于顺序一致性的常见误解。一个实例的引用在一个线程中可用这一事实并不意味着它的构造函数已设置。换句话说:读取实例与读取实例的字段是不同的操作。许多人假设一旦他们可以观察到一个实例,它就要求构造函数是 运行 但是由于缺少读取同步,这对于上面的例子来说是不正确的。

我只是稍微增加这里接受的答案 - 没有 一些 障碍绝对不能保证 一旦 你看到一个参考(想想一些线程可以获得引用)——该构造函数中的所有字段都已初始化。如果我没记错的话,我实际上已经在前一段时间回答了你的一个问题。

在具有最终字段 LoadLoadLoadStore 的构造函数之后插入了两个屏障;你想想他们的 names - 你会注意到没有操作 after 构造函数可以用一个 inside[ 重新排序=27=] 它:

Load -> Load (no Load can be re-ordered with a previous Load)
Load -> Store (no Store can be re-ordered with a previous Load)

另请注意,在当前 x86 内存模型下,您不可能打破它 - 因为它是一个(太?)强大的内存模型;因此,这些障碍在 x86free - 它们根本没有插入,因为操作没有重新排序。