Java Object Reference的发布不正确

GJ1*_*J13 21 java concurrency

下面的例子来自Brian Goetz的书"Java Concurrency in Practice",第3章,第3.5.1节.这是不正确发布对象的示例

class someClass {
    public Holder holder;

    public void initialize() {
        holder = new Holder(42);
    }
}

public class Holder {
  private int n;
  public Holder(int n) { this.n = n; }

  public void assertSanity() {
    if (n!=n)
      throw new AssertionError("This statement is false");
  }
}
Run Code Online (Sandbox Code Playgroud)

它表示Holder可能出现在另一个处于不一致状态的线程中,而另一个线程可能会观察到部分构造的对象.怎么会发生这种情况?你能用上面的例子给出一个场景吗?

此外,它继续说有一种情况,当一个线程第一次读取一个字段时可能会看到一个陈旧的值,然后下次再看到一个更新的值,这就是assertSanity可以抛出断言错误的原因.如何抛出assertionError?

从进一步阅读,解决这个问题的一种方法是通过使变量'n'最终使Holder不可变.现在,让我们假设持有人不是无法忍受的,而是有效的不可改变的.为了安全地发布这个对象,我们是否必须使holder初始化为静态并将其声明为volatile(静态初始化和volatile或者只是volatile)?就像是

public class someClass {
    public static volatile Holder holder = new Holder(42);

}
Run Code Online (Sandbox Code Playgroud)

感谢您的帮助.

Joh*_*int 12

您可以想象一个对象的创建具有许多非原子功能.首先,您要初始化并发布Holder.但是您还需要初始化所有私有成员字段并发布它们.

那么JMM没有规则来编写和发布holder成员字段 - 在写入holder字段之前发生initialie().这意味着即使holder不为null,成员字段对其他线程还不可见是合法的.

你最终可能会看到类似的东西

public class Holder{
    String someString = "foo";
    int someInt = 10;
} 
Run Code Online (Sandbox Code Playgroud)

holder可能不是null但someString可以为null,someInt可以为0.

根据我所知,在x86拱门下,这是不可能发生的,但在其他情况下可能并非如此.

所以下一个问题可能是Why does volatile fix this? JMM说在volatile列表之前发生的所有写操作都对volatile字段的所有后续线程都可见.

因此,如果holder是volatile并且您看到holder不为null,则基于volatile规则,将初始化所有字段.

要安全地发布此对象,我们是否必须将holder初始化设置为static并将其声明为volatile

是的,因为正如我所提到的,如果holder变量不为null,则所有写入都是可见的.

如何抛出assertionError?

如果一个线程注意到holder不是null,并且assertionError在进入该方法时调用并且n第一次读取可能是0(默认值),则第二次读取n现在可以看到来自第一个线程的写入.

  • 在没有 volatile 的情况下,内联静态初始化实际上是可以的(如果是这种情况,那就让它成为最终的),这是因为类初始化(不是对象发布)发生在类可以使用之前。如果它不是静态的,那么 `volatile` + 非空检查就足够了。 (2认同)