在对象构造后读取字段过时值
2022-09-02 14:09:58
我正在读一本由Brian Goetz撰写的《Java并发实践》一书。第3.5段和第3.5.1段包含我无法理解的陈述。
请考虑以下代码:
public class Holder {
private int value;
public Holder(int value) {
this.value = value;
}
public void assertValue() {
if (value != value) throw new AssertionError("Magic");
}
}
class HolderContainer {
// Unsafe publication
public Holder holder;
public void init() {
holder = new Holder(42);
}
}
作者指出:
- 在 Java 中,对象构造函数首先将默认值写入所有字段,然后再运行子类构造函数。
- 因此,可以将字段默认值视为过时值。
- 线程可能会在第一次读取字段时看到过时的值,然后在下次看到更新的值,这就是 assertN 可以抛出 AssertionError 的原因。
因此,根据文本,如果时间不走运,则值= 0;在下一刻值 = 42。
我同意第1点,对象构造函数首先用默认值填充字段。但我不明白第2点和第3点。
让我们更新作者代码并考虑以下示例:
public class Holder {
int value;
public Holder(int value) {
//Sleep to prevent constructor to finish too early
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
}
public void assertValue() {
if(value != value) System.out.println("Magic");
}
}
我添加了 Thread.sleep(3000),以强制线程等待对象完全构造。
public class Tests {
private HolderContainer hc = new HolderContainer();
class Initialization implements Runnable {
public void run() {
hc.init();
}
}
class Checking implements Runnable {
public void run() {
hc.holder.assertValue();
}
}
public void run() {
new Thread(new Initialization()).start();
new Thread(new Checking()).start();
}
}
例如:
- 第一个线程初始化持有者对象
- 第二个线程调用断言值
主线程运行两个线程:
- new Thread(new Initialization()).start();完全构造 Holder 对象需要 3 秒钟
- new Thread(new Checking()).start();由于持有者对象仍未构造代码将抛出 NullPointerException
因此,当字段具有默认值时,不可能模拟情况。
我的问题:
- 作者对这个并发问题有错吗?
- 还是无法模拟字段默认值的行为?