Java 我可以从构造函数启动线程吗?

Java 我可以从构造函数启动线程吗?,java,multithreading,Java,Multithreading,给定J5+内存模型(JSR-133),下面的代码是线程安全的并且是允许的吗 如果安全的话,在某些情况下可以接受吗 public final class BackgroundProcessor extends Object implements Runnable { public BackgroundProcessor(...) { super(); ... new Thread(this).start(); } public void run() {

给定J5+内存模型(JSR-133),下面的代码是线程安全的并且是允许的吗

如果安全的话,在某些情况下可以接受吗

public final class BackgroundProcessor
extends Object
implements Runnable
{

public BackgroundProcessor(...) {
    super();

    ...

    new Thread(this).start();
    }

public void run() {
    ...
    }
}
当我阅读新的JMM规范时,启动线程会与启动线程所做的任何事情建立一种先发生后发生的关系

假设该对象在构造函数中设置了私有成员变量,并在run()中使用

并且该类被标记为final,以防止意外的次级分类

注意:这里有一个类似的问题,但角度不同:

  • 您的类可能应该实现Runnable

  • 是的,你的代码还可以

  • 没有必要扩展对象。我确信您知道对象是由所有类隐式扩展的。我不知道这种显式扩展对象的流行做法从何而来,但这是一种糟糕的风格

  • 两个动作可以由“发生在之前”关系排序。如果一个动作发生在另一个动作之前,那么第一个动作对第二个动作可见并在第二个动作之前排序

    如果我们有两个动作x和y,我们写hb(x,y)来表示x发生在y之前

    • 如果x和y是同一线程的动作,并且x在程序顺序中位于y之前,那么hb(x,y)

    对线程的start()调用发生在已启动线程中的任何操作之前


    因此,我们可以得出结论,调用Thread.start()之前构造函数中的所有操作都发生在线程中所有已启动的操作之前。如果Thread.start()在对对象字段的所有写入之后,则启动的线程将看到对对象字段的所有写入。如果没有其他对字段的写入(另一个线程将读取),那么代码是线程安全的。

    好吧,除非您特别想在Java并发性问题上与Brian Goetz意见相左,否则我认为这是错误的。虽然他的确切措辞是“最好不要……”,但我还是会听从他的建议。从JCIP,p。42:

    “一个常见的错误,它会让 过程中的“this”引用转义 构造是开始一个线程 从构造器中[…]有 创建线程没有错 在构造函数中,但最好不是 立即启动线程。 相反,公开开始或初始化 方法……”

    更新:只是为了详细说明这是一个问题的原因。虽然保证在调用Thread.start()和线程的run()方法实际开始执行之间存在内存障碍,但在调用Thread.start()之后的构造函数期间发生的事情之间没有这样的障碍。因此,如果仍然要设置一些其他变量,或者如果JVM执行一些“have just constructed object”内务管理,那么这两个变量都不能保证被另一个线程看到:也就是说,在不完整的状态下,该对象可以被另一个线程看到


    顺便说一句,除了它是否真的破坏了JMM之外,它还感觉有点像构造函数中的“奇怪的事情”。

    我认为在构造函数中除了初始化字段之外做很多事情都不是一个好主意。这将使您的代码很难测试。这里有一篇有用的文章,比我能解释的更好。我最初把它作为一个答案,但它实际上并没有回答您的问题,因此它只是一个注释。@bm212:很好的参考(+1)。尽管如此,这是一个特例构造,我试图确定是否需要重构,以及优先级是多少。@Roman:(1)Yes-oops;(2) 我想是的;(3) 糟糕的风格只有IYO,这是你有权得到的(我相信很多人都会分享这一观点——我不知道,但我的编程风格不是这篇文章的主题)。是的,我以同样的方式阅读它,但它似乎违反了一条警告,即在构建过程中不应该让对“这”的引用漏掉(在我做过的少数情况下,我认为这没关系,我也不经常做)。在这种情况下,我在寻找线程正确性的确认,而不是好的或坏的样式-前者是需要优先修复的,后者不是。我认为您声明类final这一事实意味着这将起作用。但是,如果您(或任何其他人)一旦删除了最终声明,您就有可能不安全地发布您的对象。我会将重构作为原则问题,而不是必要性问题。是的,但为什么它不好?如果它被破坏了,我希望使用类似“您不能在构造函数中启动线程”的措辞,而不是“最好不要…”。根据Esko的回答,我倾向于认为JMM规范将在构造函数末尾启动的线程定义为线程安全的。此外,这句话是对修订后的JMM还是旧的JMM的引用?关于Goetz是指新的还是旧的JMM的问题很重要,因为JSR-133添加的内容之一是使thread.start()和thread.join()成为线程显式发生在边界之前。显然,新的内存模型——这确实是本书的重点。Thread.start()确实构成了一个before边界,但仅限于Thread.start()调用之前的代码(即不一定存储构造函数中设置的所有对象状态)发生在线程的run()之前方法。@Niel:线程启动后没有代码,我的问题是针对示例代码,其中线程启动是构造函数中的最后一件事(顺便说一句:我同意这样做很奇怪,但在我做过的几个地方确实有意义)。好吧,请注意,您正在创建一个计时炸弹。您运行的JVM可能会执行一些“对象comitting”过程(例如,comitting final字段的值)当构造函数完成时,我至少会添加一条注释,提醒未来的代码维护者意外添加这样一个字段,并且在升级VM时我会仔细测试Veery。。。