【问题标题】:Why it is bad practice to create a new thread on constructors? [duplicate]为什么在构造函数上创建新线程是不好的做法? [复制]
【发布时间】:2012-06-01 06:55:27
【问题描述】:

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

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

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

如果我的课是final的话至少是安全的吗?

编辑

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

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

        }
        ...

【问题讨论】:

  • 所述问题并非完全重复:我没有逃避这一点,也没有寻求停止我的线程的方法。我将添加代码以澄清
  • 既然我们看到了代码,我已经编辑了我的答案。你没问题,但只是因为aFieldfinal。至少有必要发表一个大评论。见下文。
  • 我的回答不正确。虽然 final 字段保证在构造函数返回时被初始化,但如果你在构造函数中 fork 线程,则不能保证 afield 将被正确初始化。我改变了我的答案。见:cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalRight

标签: java multithreading


【解决方案1】:

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

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

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

这是一个关于该主题的好链接:calling thread.start() within its own constructor

它提到了以下内容:

通过在构造函数中启动它,您肯定会违反 Java 内存模型准则。请参阅Brian Goetz's Safe Construction Techniques 了解更多信息。

编辑:

由于您的代码正在启动一个正在访问afield 的新线程,根据Java Memory Model,不能保证afield 在线程开始运行时会被正确初始化。

我建议改为在您的类上添加一个start() 方法,该方法调用thread.start()。这是一种更好的做法,并且可以让使用此类的其他类更容易看到在构造函数中创建线程。

【讨论】:

  • 在我启动线程之前,我在构造函数本身中设置了内部类线程使用的外部类的所有字段。还是有可能编译器改变我的指令顺序?
  • 保证违反准则”并没有为灵活性留下太多余地。
  • 是的。无法保证在构造函数完成之前任何非最终字段都将正确初始化。这就是比赛的全部意义所在。这意味着您的其他线程可能会处理尚未完全初始化的对象,即使它是在构造函数的最后一行启动的。
  • 谢谢格雷,你说得非常清楚。我想我会将线程开始移动到单例的静态访问器
  • 我的理解是,如果在构造函数中启动线程,即使是最终字段也不会被安全地初始化。如果对对象的引用从构造函数中转义,则最终变量的所有安全发布规则都将失效。
【解决方案2】:

一般来说,最好对你在构造函数中所做的事情保持温和。

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

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

【讨论】:

  • 谢谢,我会看的。我知道在我的构造函数中保持温和的必要性。线程是一个内部类,只使用启动时已经初始化的主类的字段。
  • 那更糟,因为内部类可以完全访问所有内容。它可能工作正常,但您将自己暴露在各种可怕的事情中。
【解决方案3】:

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

【讨论】:

  • 我认为 Andrea Parodi 要求比较 Thread t = new Runnable ( ) { public void run ( ) { /*do stuff*/} } ; t . start ( ) ;new Runnable ( ) { { start ( ) ; } public void run ( ) { /*do stuff*/} } ;。在这两种情况下,线程的开销是相同的。例如,如果您使用匿名类来创建一次性线程,为什么不直接在其初始化块中启动线程,甚至不需要保留对它的引用。这是我以前做过的事情,现在感谢其他答案,我知道为什么不这样做。
  • @Garret:我启动线程的类是单例,所以你的问题不适用。
猜你喜欢
  • 2013-08-23
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-08
  • 2015-06-10
相关资源
最近更新 更多