2

可能重复:
Java:为什么不在构造函数中启动线程?如何终止?

我习惯于在我的代码上运行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();

        }
        ...
4

3 回答 3

5

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

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

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

这是关于该主题的一个很好的链接:在其自己的构造函数中调用 thread.start()

它提到以下内容:

通过从构造函数中启动它,您肯定会违反 Java 内存模型准则。有关更多信息,请参阅Brian Goetz 的安全施工技术

编辑:

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

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

于 2012-05-25T17:18:39.470 回答
2

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

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

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

于 2012-05-25T17:18:53.700 回答
0

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

于 2012-05-25T17:18:22.577 回答