为什么我不应该在我的类的构造函数中使用Thread.start()? [英] Why shouldn't I use Thread.start() in the constructor of my class?

查看:147
本文介绍了为什么我不应该在我的类的构造函数中使用Thread.start()?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在寻找为什么不应该在类的构造函数中调用线程的start方法。考虑下面的代码:

  class SomeClass 
{
public ImportantData data = null;
public Thread t = null;

public SomeClass(Important Data d)
{
t = new MyOperationThread();

// t.start(); // Footnote 1

data = d;

t.start(); // Footnote 2
}
}

ImportantData是一些通用的(可能很重要),MyOperationThread是知道如何处理SomeClass实例的线程的子类。



脚本:


  1. 我完全明白为什么这是不安全的。如果MyOperationThread试图在下面的语句完成之前访问SomeClass.data(并且数据被初始化),我会得到一个异常,否则我没有准备好。或者也许我不会。你不能总是告诉线程。


  2. 我不明白为什么这样做是被禁止的领地。此时,所有SomeClass的成员都已经初始化,没有其他改变状态的成员函数被调用,因此有效地完成了构造。


根据我的理解,认为糟糕的做法是,你可以泄漏一个尚未完全构建的对象的引用。但是对象已经被完全构造,构造函数没有什么可做,但返回。我已经搜索了其他问题寻找一个更具体的答案这个问题,并已经调查参考材料,但没有发现任何说你不应该,因为这样的不良行为,只有说你不应该。



如何在构造函数中启动一个线程在概念上与这种情况不同:

  class SomeClass 
{
public ImportantData data = null;

public SomeClass(Important Data d)
{
// OtherClass.someExternalOperation(this); //不是一个好主意

data = d;

OtherClass.someExternalOperation(this); //通常接受为OK
}
}

如果类是final?

  final class SomeClass // like this 
{
...

我看到很多问题问这个问题,答案你不应该,但没有提供解释,我想我会尝试添加一个更多的细节。

解决方案


对象已经完全构造,构造函数没有什么可做,但返回


是和否。问题是根据Java存储器模型,编译器能够重新排序构造器操作,并且在构造器完成之后实际完成对象的构造器。 > >将确保在构造函数完成之前初始化 volatile final 不保证(例如)您的 ImportantData数据字段将在构造函数完成时正确初始化。



如@meriton在注释中指出,在与线程和启动它的线程关系之前发生。在#2的情况下,你很好,因为 data 必须在线程启动之前完全分配。这是根据Java内存模型保证的。

也就是说,它被认为是坏的做法,泄漏对其构造函数中的对象的引用到另一个线程,因为如果在 t.start()之后
之后添加任何构造函数行,如果线程看到对象已完全构造,则将是竞争条件。



以下是一些更多的信息:




I've been searching for justification as for why you should not call a thread's start method inside a constructor for a class. Consider the following code:

class SomeClass
{
    public ImportantData data = null;
    public Thread t = null;

    public SomeClass(ImportantData d)
    {
        t = new MyOperationThread();

        // t.start(); // Footnote 1

        data = d;

        t.start();    // Footnote 2
    }
}

ImportantData is some generic box of stuff (presumably important) and MyOperationThread is a subclass of thread that knows how to handle SomeClass instances.

Footnodes:

  1. I totally understand why this is unsafe. If the MyOperationThread tries to access SomeClass.data before the following statement finishes (and data is initialized) I'll get an exception that I was otherwise unprepared for. Or maybe I won't. You can't always tell with threads. In any case, I'm setting myself up for weird, unexpected behavior later.

  2. I don't understand why doing it this way is forbidden territory. At this point, all of SomeClass' members have been initialized, no other member functions that change state have been called, and construction is thus effectively finished.

From what I understand, the reason it's considered bad practice to do this is that you can "leak a reference to an object that has not yet been fully constructed." But the object has been fully constructed, the constructor has nothing left to do but return. I have searched other questions looking for a more concrete answer to this question, and have looked into referenced material as well, but haven't found anything that says "you shouldn't because such and such undesirable behavior," only things that say "you shouldn't."

How would starting a thread in the constructor be conceptually different from this situation:

class SomeClass
{
    public ImportantData data = null;

    public SomeClass(ImportantData d)
    {
        // OtherClass.someExternalOperation(this); // Not a good idea

        data = d;

        OtherClass.someExternalOperation(this);    // Usually accepted as OK
    }
}

As another aside, what if the class was final?

final class SomeClass // like this
{
    ...

I saw plenty of questions asking about this and answers that you shouldn't, but none offered explanations, so I figured I'd try to add one that has a few more details.

解决方案

But the object has been fully constructed, the constructor has nothing left to do but return

Yes and no. The problem is that according to the Java memory model, the compiler is able to reorder the constructor operations and actually finish the constructor of the object after the constructor finishes. volatile or final fields will be guaranteed to be initialized before the constructor finishes but there is no guarantee that (for example) your ImportantData data field will be properly initialized by the time the constructor finishes.

However as @meriton pointed out in comments, there is a happens before relationship with a thread and the thread that started it. In the case of #2, you are fine because data has to be assigned fully before the thread is started. This is guaranteed according to the Java memory model.

That said, it is considered bad practice to "leak" a reference to an object in its constructor to another thread because if any constructor lines were added after the t.start() it would be a race condition if the thread would see the object full constructed or not.

Here's some more reading:

这篇关于为什么我不应该在我的类的构造函数中使用Thread.start()?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆