Java:为什么不应该允许它转义构造函数?



在 JCIP 第 3.2.1 节"安全构造函数实践"中,有一个警告,不要从构造函数向另一个线程泄漏this,"即使发布是构造函数中的最后一条语句。最后一部分对我来说似乎太强大了,而且没有理由。施工后发生了什么,我必须如此小心地避免?有例外吗?我很感兴趣,因为我最近提交了一些代码,其中我做了这件事,我想决定是否有理由回过头来重构。

就Java内存模型而言,构造函数出口在最终字段语义中起着重要作用,因此语句是在构造函数退出之前还是之后是有区别的。

This works                         This doesn't work
-------------------------------------------------------------
static Foo shared;                 static Foo shared;
class Foo                          class Foo
{                                  {   
final int i;                       final int i; 
Foo()                              Foo() 
{                                  {
i = 1;                             i = 1;
shared = this;  
}                                  }
}                                  }
shared = new Foo();                new Foo();

(注意:shared不是易失性的;发布是通过数据竞赛进行的。

这两个示例之间的唯一区别是在构造函数退出之前或之后分配shared。在第二个示例中,允许在分配后对i=1进行重新排序。

但是,如果发布是一个同步操作,例如通过一个易失变量,那么没关系;其他线程将观察一个完全初始化的对象;字段甚至不必final

通过数据竞赛(或通过数据竞赛做任何事情)发布是一项非常棘手的业务,需要非常仔细的推理。如果你避免数据竞争,事情就会简单得多。如果您的代码不包含数据争用,则在构造函数退出之前立即泄漏this与在构造函数退出后立即发布它之间没有区别。

你永远不应该在任何时候从构造函数泄漏this,"甚至 [...]在最后一句话中。由于this没有完全构建,因此可能会发生一些非常奇怪的事情。在一个非常相似的问题上看到这个SO答案。

你永远不应该从构造函数中传递this(称为"泄漏this")

即使它是构造函数的最后一行,您也不应该这样做的一个原因是,只要对当前线程的影响不受影响,JVM 就可以对语句重新排序。如果将this传递给在另一个线程中运行的进程,则重新排序可能会导致奇怪而微妙的错误。

另一个原因是子类可能提供自己的初始化,因此构造可能无法在类构造函数的最后一行完成。

相关内容

最新更新