在 JCIP 的第 3.2.1 节“安全构造函数实践”中,有一个警告,防止this
从构造函数泄漏到另一个线程,“即使发布是构造函数中的最后一条语句”。最后一部分对我来说似乎太强了,而且没有任何理由。施工后会发生什么我必须非常小心避免的事情?有例外吗?我很感兴趣,因为我最近提交了一些代码,我在其中做了这件事,我想决定是否有理由回顾和重构。
3 回答
就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
允许在分配后重新排序。
但是,如果发布是同步操作,例如通过 volatile 变量,则可以;其他线程将观察一个完全初始化的对象;字段甚至不必final
。
通过数据竞赛发布(或通过数据竞赛做任何事情)是一项非常棘手的业务,需要非常仔细的推理。如果避免数据竞争,事情就会简单得多。如果您的代码不包含数据竞争,那么在构造函数退出之前立即泄漏和在构造函数退出后立即发布它没有区别。this
你永远不应该this
在任何时候从构造函数中泄漏,“甚至在最后一个语句中 [...]。” 由于this
没有完全构建,可能会发生一些非常奇怪的事情。在一个非常相似的问题上看到这个 SO 答案。
你永远不应该this
离开构造函数(称为“泄漏this
”)
即使是构造函数的最后一行,也不应该这样做的一个原因是,只要不影响对当前线程的影响,JVM 就可以重新排序语句。如果this
传递给在另一个线程中运行的进程,重新排序可能会导致奇怪和微妙的错误。
另一个原因是子类可能会提供自己的初始化,因此在类的构造函数的最后一行可能没有完成构造。