<分区>
根据 Java Concurrency in Practice,在类构造函数中启动线程是危险的。原因是在对象完全构造之前,this 将 this
指针暴露给另一个线程。
尽管这个话题在之前的许多 StackOverflow 问题中都有讨论,但我仍然难以理解为什么这是一个如此令人担忧的问题。特别是,我希望从 Java 内存模型的角度澄清在构造函数内启动线程是否会导致内存一致性问题。
让我举一个具体的例子来说明我想做的事情。 (这段代码的期望输出是将数字 20 打印到控制台。)
private static class ValueHolder {
private int value;
private Thread thread;
ValueHolder() {
this.value = 10;
thread = new Thread(new DoublingTask(this)); // exposing "this" pointer!!!
thread.start(); // starting thread inside constructor!!!
}
int getValue() {
return value;
}
void awaitTermination() {
try {
thread.join();
} catch (InterruptedException ex) {}
}
}
private static class DoublingTask implements Runnable {
private ValueHolder valueHolder;
DoublingTask(ValueHolder valueHolder) {
this.valueHolder = valueHolder;
}
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println(valueHolder.getValue() * 2); // I expect to print out 20...
}
}
public static void main(String[] args) {
ValueHolder myValueHolder = new ValueHolder();
myValueHolder.awaitTermination();
}
是的,我知道线程是在我们从构造函数返回之前启动的。是的,我知道 this
指针暴露给了线程。 然而,我确信代码是正确的。我相信它会始终将数字 20 打印到控制台。
- 赋值
this.value = 10
happens-beforethread.start()
。 (这是因为this.value = 10
在程序顺序中位于thread.start()
之前。) - 主线程中
thread.start()
的调用发生在.run()
方法开始之前新创建的线程。 (因为thread.start()
是一个同步 Action 。) .run()
方法的开始发生在System.out.println(valueHolder.getValue() * 2);
打印语句。 (同样,按节目顺序。)
因此,根据 Java 内存模型,打印语句应该读取 valueHolder.value
的正确初始化值(即 10)。因此,尽管忽略了Java Concurrency in Practice 的建议,我似乎仍然编写了一段正确的代码。
我做错了吗?我错过了什么?
更新:根据回答和评论,我现在明白我的代码示例在功能上是正确的,因为我提供的原因。但是,以这种方式编写代码是一种不好的做法,因为其他开发人员将来有可能在线程启动后添加更多的初始化语句。可能出现此类错误的一种情况是在实现此类的子类时。