java - 为什么我不应该在类的构造函数中使用 Thread.start()?

标签 java multithreading constructor thread-safety

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

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 是一些通用的东西(可能很重要),而 MyOperationThread 是知道如何处理 SomeClass 实例的线程的子类。

脚节点:

  1. 我完全理解为什么这是不安全的。如果 MyOperationThread 尝试在以下语句完成(并且数据已初始化)之前访问 SomeClass.data,我将得到一个我没有准备好的异常。或者也许我不会。你不能总是用线程来判断。无论如何,我都在为以后奇怪的、意想不到的行为做准备。

  2. 我不明白为什么这样做是禁区。至此,SomeClass的所有成员都已经初始化完毕,没有调用其他改变状态的成员函数,从而有效构造完成。

据我所知,这样做被认为是不好的做法的原因是您可以“泄漏对尚未完全构造的对象的引用”。但是对象已经完全构建,构造函数除了返回之外别无他法。我搜索了其他问题以寻找该问题的更具体答案,并且也查看了引用资料,但没有找到任何说“你不应该因为这样那样的不良行为”,只有说“你不应该。”

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

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
    }
}

另一方面,如果类(class)是最终的呢?

final class SomeClass // like this
{
    ...

我看到很多关于此的问题和不应该回答的问题,但没有人提供解释,所以我想我应该尝试添加一个包含更多详细信息的问题。

最佳答案

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

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

但是正如@meriton 在评论中指出的那样,在与线程和启动它的线程的关系之前发生了关系。在#2 的情况下,你没问题,因为 data 必须在线程启动之前完全分配。这是根据 Java 内存模型保证的。

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

这里还有一些阅读:

关于java - 为什么我不应该在类的构造函数中使用 Thread.start()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11834173/

相关文章:

java - 将 Jtable 行字体更改为粗体

java - GridView android suffle 项目滚动时

c - 如何编写检查竞争条件的测试?

c++ - 区分两个零参数构造函数的惯用方法

c# - C# 中的静态构造函数

Java在ValueOf(double/float)方法中不提供DoubleCache或FloatCache

java - 在 GWTP 中的 RPC 调用期间显示 "loading indicator"的最简单方法是什么?

vb.net - VB.Net多线程文件下载

c++ - boost::lockfree::spsc_queue、boost::lockfree::queue、串行队列操作之间的比较

c++ - 从复制构造函数调用构造函数时,它也会调用析构函数