为什么在构造函数上创建新线程是不好的做法?

时间:2012-05-25 17:14:22

标签: java multithreading

  

可能重复:
  Java: Why not to start a thread in the constructor? How to terminate?

我习惯在我的代码上运行FindBugs以查找错误或不良做法。 今天它抱怨我正在类构造函数中启动一个线程。

真的是坏事吗?你能解释一下为什么吗?

如果我的班级是最终的,那至少是安全的吗?

编辑

该线程是作为内部类实现的,它只使用在启动时已经初始化的主类的字段:

public final class SingletonOuter {
    private static SingletonOuter ourInstance = new SingletonOuter();

    public static SingletonOuter getInstance() {
        return ourInstance;
    }

    private final SomeOtherClass aField;

    private SingletonOuter() {
        aField=new SomeOtherClass(); 
        thread=new InnerThread();
        thread.start();
    }

    private boolean pleaseStop;

    private synchronized boolean askedStop(){return pleaseStop;}
    public synchronized void stop(){
        pleaseStop=true;  
    }

    private final InnerThread thread ;
    private class InnerThread extends Thread{
        @Override public void run() {
            //do stuff with aField until askedStop()
        }
    }

}

修改

我最后将线程的开始移动到getInstance方法,以避免引入未来错误的可能性:

public final class SingletonOuter {
        private static SingletonOuter ourInstance

        public static SingletonOuter getInstance() {
            if (ourInstance==null){
                ourInstance= = new SingletonOuter();
                ourInstance.thread.start();
            }

            return ourInstance;
        }

        private final SomeOtherClass aField;

        private SingletonOuter() {
            aField=new SomeOtherClass(); 
            thread=new InnerThread();

        }
        ...

3 个答案:

答案 0 :(得分:5)

  

为什么在构造函数上创建新线程是不好的做法?

Findbugs提醒您注意对象构造周围的指令重新排序问题。虽然已分配新对象的内存空间,但无法保证在InnerThread启动时已初始化任何字段。虽然final字段在构造函数完成之前初始化,但无法保证InnerThread在启动时开始使用(例如)aField,它将被初始化。 Java编译器出于性能原因执行此操作。它还可以选择在构造函数返回新实例后将非final字段的初始化移动到

如果在构造函数中启动一个新线程,那么该线程可能会处理一个部分初始化的对象。即使thread.start()是构造函数中的最后一个语句,由于重新排序,新线程可能正在访问部分构造的对象。这是Java语言规范的一部分。

以下是关于该主题的良好链接:calling thread.start() within its own constructor

它提到了以下内容:

  

通过在构造函数中启动它,可以保证违反Java Memory Model指南。有关详细信息,请参阅Brian Goetz's Safe Construction Techniques

修改

由于您的代码正在启动正在访问afield的新线程,因此根据Java Memory Model,无法保证在线程开始运行时afield将被正确初始化。 / p>

我建议您在课程中添加start()方法,调用thread.start()。这是一种更好的做法,使得使用此类的其他类更容易看到在构造函数中创建线程。

答案 1 :(得分:2)

一般来说,最好对构造函数中的操作保持温和。

您的对象仍处于无效状态,因此您不希望任何人访问它。当你从构造函数启动一个线程时,它可能会引用正在构造的对象(否则为什么构造函数会启动它?)。此引用将指向线程启动时及其生效后不久的无效对象。那里有可怕的竞争条件。

以下是一篇关于它的好文章的链接http://www.ibm.com/developerworks/java/library/j-jtp0618/index.html

答案 2 :(得分:0)

每次实例化该类时都会创建一个线程。线程很昂贵,很难测试。如果实例化许多对象会遇到性能问题,则应考虑使用ThreadPool来修复线程数限制。此外,如果您在尝试对线程中发生的任何行为进行单元测试时遇到问题。