java - 了解为什么根据 Java 内存模型在构造函数中启动线程是不安全的
问题描述
根据Java Concurrency in Practice,在类构造函数中启动线程是危险的。原因是这会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
发生在之前thread.start()
。(这是因为程序顺序this.value = 10
在前面thread.start()
。) thread.start()
主线程中的调用发生.run()
在新创建线程中的方法开始之前。(因为thread.start()
是同步动作。)- 该
.run()
方法的开始发生在打印System.out.println(valueHolder.getValue() * 2);
语句之前。(同样,按程序顺序。)
因此,根据 Java 内存模型,print 语句应该读取正确初始化的值valueHolder.value
(即 10)。因此,尽管忽略了Java Concurrency in Practice的建议,但我似乎仍然编写了一段正确的代码。
我犯错了吗?我错过了什么?
更新:根据答案和评论,我现在明白,由于我提供的原因,我的代码示例在功能上是正确的。但是,以这种方式编写代码是不好的做法,因为其他开发人员将来有可能在线程启动后添加进一步的初始化语句。可能出现此类错误的一种情况是在实现此类的子类时。
解决方案
假设我将你的类子类化。它可能在需要时尚未初始化其字段。
class BetterValueHolder extends ValueHolder
{
private int betterValue;
BetterValueHolder(final int betterValue)
{
// not actually required, it's added implicitly anyway.
// just to demonstrate where your constructor is called from
super();
try
{
Thread.sleep(1000); // just to demonstrate the race condition more clearly
}
catch (InterruptedException e) {}
this.betterValue = betterValue;
}
@Override
public int getValue()
{
return betterValue;
}
}
这将打印零,无论BetterValueHolder
.
推荐阅读
- wordpress - 从 URL 结构中删除安装 WordPress 的子目录
- java - 为什么 javac 在这种情况下不检查未声明的 TypeParameter?
- c++ - 在 C++ 中实现链表的问题
- c# - 如何使用剃刀页面访问代码中的输入文本框
- c# - Web api 中的 System.Web.HttpContext
- python-3.x - 如何使用命名空间从 XML 中提取嵌套值?
- swiftui - 如何在 SwiftUI 中同时实现声音和振动
- javascript - Puppeteer 中的数组为空
- r - R Studio 服务器中的箭头
- php - 在 PHP 中将字符串评估为正则表达式