我一直在寻找理由,为什么你不应该在类的构造函数中调用线程的start方法。请考虑以下代码: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实例的线程的子类。Footnodes:我完全理解为什么这是不安全的。如果MyOperationThread在下面的语句完成之前尝试访问SomeClass.data(并且数据被初始化),那么我将得到一个我没有准备好的异常。或许我不会。你不能总是告诉线程。在任何情况下,我都会为以后的奇怪,意外行为做好准备。我不明白为什么这样做是禁止的领土。此时,所有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
}}另外,如果课程是最终的怎么办?final class SomeClass // like this{
...我看到很多问题都在询问这个和你不应该的答案,但没有人提供解释,所以我想我会尝试添加一个有更多细节的答案。
2 回答

有只小跳蛙
TA贡献1824条经验 获得超8个赞
但是对象已经完全构造,构造函数除了返回之外没有什么可做的
是的,不是。问题是,根据Java内存模型,编译器能够重新排序构造函数操作,并在构造函数完成后实际完成对象的构造函数。 volatile
或final
领域将得到保证构造完成之前被初始化,但没有保证(比如)您的ImportantData data
领域将得到妥善的构造函数结束时被初始化。
然而正如@meriton在评论中指出的那样,在与线程和启动它的线程的关系之前会发生一些事情。在#2的情况下,你很好,因为data
必须在线程启动之前完全分配。这是根据Java内存模型保证的。
也就是说,将对其构造函数中的对象的引用“泄漏”到另一个线程被认为是不好的做法,因为如果在线程看到对象完全构造与否之后,t.start()
如果在它之后添加任何构造函数线将是竞争条件。
这里有更多的阅读:
这是一个很好的问题:在自己的构造函数中调用thread.start()
Doug Lea的内存模型页面讨论了指令重新排序和构造函数。
这是关于安全构造函数实践的一篇很棒的文章,它更多地讨论了这一点。
这就是为什么“双重检查锁定”问题也存在问题的原因。
我对这个问题的回答是相关的:这是对象的安全发布吗?
添加回答
举报
0/150
提交
取消