将一个线程作为最终类的构造函数的最后一个语句

时间:2012-03-29 11:02:24

标签: java multithreading concurrency

我理解一般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();
}

修改
我认为之前没有问过这个问题,因为问题比“我可以在构造函数中启动线程”更具体:线程是在构造函数的最后一个语句中启动的,这意味着对象构造已经完成(据我所知)。

4 个答案:

答案 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块是多余的,可以忽略。