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

Java 为什么在构造函数上创建新线程是不好的做法?,java,multithreading,Java,Multithreading,可能重复: 我习惯于在我的代码上运行以发现bug或错误实践。 今天,它抱怨我正在类构造函数中启动一个线程 这真的是件坏事吗?你能解释一下为什么吗 如果我的课是期末考试,至少是安全的 编辑: 线程作为一个内部类实现,它只使用启动时已初始化的主类的字段: public final class SingletonOuter { private static SingletonOuter ourInstance = new SingletonOuter(); public stati

可能重复:

我习惯于在我的代码上运行以发现bug或错误实践。 今天,它抱怨我正在类构造函数中启动一个线程

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

如果我的课是期末考试,至少是安全的

编辑

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

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方法,以避免将来引入bug的可能性:

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();

        }
        ...

每次实例化该类时,都会创建一个线程。线程非常昂贵,而且很难测试。如果实例化许多对象,就会遇到性能问题,应该考虑线程池来限制线程的数量。此外,如果您在尝试单元测试线程中发生的任何行为时遇到困难

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

Findbugs正在提醒您有关对象构造的指令重新排序可能性的问题。虽然为新对象分配了内存空间,但不能保证在
InnerThread
启动时任何字段都已初始化。尽管在构造函数完成之前,
final
字段将被初始化,但不能保证如果
InnerThread
在启动时开始使用(例如)
aField
,它将被初始化。Java编译器这样做是出于性能原因。它还可以选择在构造函数返回新实例后将非final字段的初始化移动到

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

这里有一个关于该主题的好链接:

它提到以下几点:

通过在构造函数中启动它,可以保证违反Java内存模型准则。有关更多信息,请参阅

编辑:

由于您的代码正在启动一个正在访问
afield
的新线程,因此根据,无法保证线程开始运行时
afield
会正确初始化


相反,我建议在类中添加一个
start()
方法来调用
thread.start()
。这是一个更好的实践,并使使用该类的其他类更容易看到在构造函数中创建线程。

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

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


这是一篇关于它的好文章的链接

谢谢,我会读的。我意识到对我的施工人员要温柔。线程是一个内部类,在启动时只使用已经初始化的主类的字段。这更糟糕,因为内部类拥有对所有内容的完全访问权限。它可能工作正常,但你正在暴露自己于各种可怕的事情中;T开始()
newrunnable(){{start();}public void run(){/*do stuff*/}。在这两种情况下,线程的开销是相同的。例如,如果您使用匿名类创建一次性线程,为什么不在其初始化块中启动线程,甚至不需要保留对它的引用呢。这是我以前做过的事情,现在多亏了其他答案,我知道为什么不。@Garret:我从中启动线程的类是一个单实例,所以你的问题不适用。在启动线程之前,我正在构造函数中设置内部类线程使用的外部类的所有字段。编译器仍然有可能更改我的指令顺序,“保证违反指导原则”不会给灵活性留下太多余地。是的。无法保证在构造函数完成之前,任何非最终字段都会正确初始化。这就是比赛的重点。这意味着您的另一个线程可能会处理尚未完全初始化的对象,即使它是在构造函数的最后一行启动的。我想我会把线程的开始移到Singletone的静态访问器,我的理解是,如果线程是在构造函数中启动的,即使是最后的字段也不会被安全地初始化。如果构造函数漏掉了对对象的引用,则最终变量的所有安全发布规则都将无效。所述问题不是完全重复的:我不会漏掉它,也不会要求停止线程的方法。我将向Clarify添加代码现在我们看到了代码,我已经编辑了我的答案。您还可以,但这只是因为
aField
final
。至少一个大评论是有道理的。见下文。我的回答不正确。尽管最终字段保证在构造函数返回时初始化,但如果您在构造函数中分叉线程,则不能保证