下面的例子来自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");
}
}
它说,Holder可能会出现在另一个处于不一致状态的线程中,而另一个线程可能会观察到一个部分构造的对象。这怎么会发生呢?你能用上面的例子给出一个场景吗?
此外,它还指出,在某些情况下,线程可能会在第一次读取字段时看到过时的值,然后在下一次看到更新的值,这就是为什么assertSanity
会抛出AssertionError
。如何抛出AssertionError
?
根据进一步的阅读,解决这个问题的一种方法是通过使变量n
final来使Holder
不可变。现在,让我们假设Holder
不是不可变的,但实际上是不可变的。
为了安全地发布这个对象,我们是否必须将holder初始化设为静态,并将其声明为易失性(静态初始化和易失性或只声明易失性)?
如下所示:
public class SomeClass {
public static volatile Holder holder = new Holder(42);
}
发布于 2013-04-20 00:11:41
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
可以在构造函数返回之后发生。
这意味着赋值可能看起来发生在第一次读取之后的第二个线程中,而在第二次读取之前,从而导致值不一致。可以通过将n
设为final来防止这种情况,这可以保证在构造函数完成之前赋值。
发布于 2013-04-20 00:00:55
Holder
类没有问题,但是someClass
类可能处于不一致的状态--在创建和调用initialize()
之间,holder
实例变量为null
。
https://stackoverflow.com/questions/16107683
复制相似问题