【发布时间】:2018-11-12 07:54:33
【问题描述】:
我正在阅读 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 中,Object 构造函数在子类构造函数运行之前首先将默认值写入所有字段。
- 因此,可能会将字段默认值视为陈旧值。
- 线程第一次读取字段时可能会看到陈旧的值,然后在下一次读取更新的值,这就是为什么 assertN 会抛出 AssertionError。
因此,根据文本,在一些不走运的时机可能 value = 0;下一刻 value = 42。
我同意第 1 点,即 Object 构造函数首先用默认值填充字段。但我不明白第 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();
}
}
例如:
- 第一个线程初始化持有者对象
- 第二个线程调用assertValue
主线程运行两个线程:
- 新线程(新初始化()).start();完全构建 Holder 对象需要 3 秒
- 新线程(新检查()).start();由于 Holder 对象仍未构造,代码将抛出 NullPointerException
因此,无法模拟字段具有默认值的情况。
我的问题:
- 作者对这个并发问题有误?
- 或者无法模拟字段默认值的行为?
【问题讨论】:
-
@VinceEmigh,我已经在 Java 6 和 Java 8 上测试过了
-
@AxelH 正如我所写:“new Thread(new Checking()).start(); 因为 Holder 对象仍未构造,代码将引发异常”。我的意思是我们将拥有 NPE。
-
那么可能需要检查更多的测试用例。明白了;)
-
只有在线程从构造函数内部启动时才有可能 - see here。我能够使用链接中的示例触发异常,但只有当我将断言更改为
if (value != 42)。
标签: java multithreading concurrency java-memory-model safe-publication