我理解一般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);
}
}
打印(每次运行显然不一样):
施工OK =假
施工OK =假
施工OK =假
施工OK =假
施工OK =假
施工OK =假
施工OK =假
施工OK = true
施工OK = true
施工OK = true
现在 IF start
方法是构造函数的最后一个语句 AND 通过在最终值初始化周围使用synchronized块来防止重新排序,是否存在仍然存在与从构造函数启动线程相关的风险?
public Test(int value) throws InterruptedException {
synchronized (new Object()) { // to prevent reordering + no deadlock risk
this.value = value;
}
start();
}
修改
我认为之前没有问过这个问题,因为问题比“我可以在构造函数中启动线程”更具体:线程是在构造函数的最后一个语句中启动的,这意味着对象构造已经完成(据我所知)。
答案 0 :(得分:4)
是的,因为Test
可以被子类化,然后在创建实例之前将{em}执行start()
。子类构造函数可能还有更多工作要做。
所以课程至少应为final
。
答案 1 :(得分:1)
在这种特殊情况下,我会考虑将value
标记为volatile
(或使用AtomicBoolean
)并在设置值后启动线程:
this.value = value; // this.value.set(value) if using AtomicBoolean
start();
如果采用这种稍微狡猾的解决方案,我也会制作课程final
,以避免Andreas_D所描述的问题。
关于您的修改:
[...]表示对象构造已完成(据我所知)。
这是对的,但请考虑以下情况:
您的测试线程稍微复杂一些,并访问测试列表testList
。现在,如果你这样做
testList.add(new Test());
在构造函数中启动的线程可能无法在列表中找到关联的测试,因为它尚未添加。相反,这可以通过
来避免Test t = new Test();
testList.add(t);
t.start();
相关问题:
答案 2 :(得分:0)
在构造函数中,您可以通过
调用start方法start()
班上的。现在你可以注意到你调用的方法是这个类的一个尚未创建的对象。因此,您仍然将未构造对象的引用传递给方法。你已经在创建对象时包含了methodcall本身,而在完全构造对象之后应该调用对象的任何方法。
所以仍有风险。 这也是一个非常好的问题。
答案 3 :(得分:0)
synchronized(new Object())
不会阻止重新排序 - 因为监视器是局部变量,编译器实际上可以自由忽略同步块。
特别是,编译器可以证明两个threds不可能锁定在同一个监视器上(通过定义局部变量),因此synchronized块是多余的,可以忽略。