java将线程作为最终类构造函数的最后一条语句启动
我理解,总的来说it is a bad idea to start a new thread in a constructor,因为它可能在完全构建之前就让这个逃逸了。例如:
public final class Test {
private final int value;
public Test(int value) throws InterruptedException {
start();
this.value = value;
}
private void start() throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Construction OK = " + Boolean.toString(Test.this.value == 5));
}
}).start();
}
}
public static void main(String[] args) throws InterruptedException {
Test test = new Test(5);
}
}
这张照片(显然不是每次跑步都一样):
Construction OK = false
Construction OK = false
Construction OK = false
Construction OK = false
Construction OK = false
Construction OK = false
Construction OK = false
Construction OK = true
Construction OK = true
Construction OK = true
现在如果start
方法是构造函数的最后一条语句并且通过在最终值初始化周围使用同步块来防止重新排序,那么仍然存在与从构造函数启动线程相关的风险吗
public Test(int value) throws InterruptedException {
synchronized (new Object()) { // to prevent reordering + no deadlock risk
this.value = value;
}
start();
}
编辑
我不认为以前有人问过这个问题,因为这个问题比“我可以在构造函数中启动线程吗?”更具体:线程在构造函数的最后一个语句中启动,这意味着对象构造已经完成(据我所知)
# 1 楼答案
synchronized(new Object())
不会阻止重新排序——因为监视器是一个局部变量,编译器实际上可以忽略同步块特别是,编译器可以证明两个THRED不可能锁定在同一个监视器上(根据局部变量的定义),因此同步块是冗余的,可以忽略
# 2 楼答案
在构造函数中,调用start方法
全班同学。现在您可以注意到,您正在调用的方法是这个类的一个尚未创建的对象。因此,您仍在将未构造对象的引用传递给方法。在创建对象时,您已经包含了methodcall本身,而对象的任何方法都应该在对象完全构造之后调用
因此,仍然存在与之相关的风险。 这也是一个非常好的问题
# 3 楼答案
是的,因为
Test
可以被子类化,然后start()
将在创建实例之前执行。子类构造函数可能还有更多的工作要做所以这个类至少应该是
final
# 4 楼答案
在这种特殊情况下,我会考虑标记{{CD1>}为^ {< CD2>}(或使用^{} ),并在设置值之后启动线程:
如果使用这个稍微有点不靠谱的解决方案,我也会使用
final
类,以避免Andreas_D描述的问题关于您的编辑:
这是正确的,但是考虑下面的场景:
您的测试线程稍微复杂一些,可以访问测试列表
testList
。现在如果你这么做了在构造函数中启动的线程可能在列表中找不到关联的测试,因为它尚未被添加。这样做是可以避免的
相关问题: