Java Object Reference的发布不正确

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (40)

下面的例子来自Brian Goetz的书籍“Java并发实践”,Chapte 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");
  }
}

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

还有一种情况是,有些情况下线程可能会在第一次读取字段时看到一个陈旧的值,并且下一次会看到更新的值,这就是为什么assertSanity会抛出Assertion Error。assertionError如何被抛出?

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

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

}
提问于
用户回答回答于

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

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

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

public class Holder{
    String someString = "foo";
    int someInt = 10;
} 

holder可能不为null,但someString可能为空,someInt可能为0。

在x86架构下,据我所知,这是不可能发生的,但在其他情况下可能并非如此。

因此,下一个问题可能是Why does volatile fix this? JMM说,易失性存储之前发生的所有写入对易失性字段的所有后续线程均可见。

因此,如果holder是易变的,并且您看到holder的不是null,则基于易失规则,所有字段都将被初始化。

为了安全地发布这个对象,我们是否必须让持有者初始化为静态并声明它是不稳定的

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

assertionError如何被抛出?

如果线程通知holder不为空,并且assertionError在进入方法时调用并且n第一次读取可以是0(默认值),则第二次读取n可以看到来自第一线程的写入。

用户回答回答于
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");
  }
}

假设一个线程创建一个实例Holder,并将引用传递给另一个调用的线程assertSanity

this.n在构造函数中的赋值发生在一个线程中。n另一个线程中出现两次读取。唯一的事情发生在两个阅读之间的关系之前。没有涉及分配和任何读取的发生之前的关系。

在关系之前没有任何发生的情况下,可以以各种方式对语句进行重新排序,因此从一个线程的角度来看,this.n = n可以在构造函数返回后进行。

这意味着赋值可能会出现在第一个读取后的第二个线程中,并在第二个线程之前出现,从而导致值不一致。可以通过使nfinal 来防止,这可以确保在构造函数完成之前分配值。

扫码关注云+社区