在JCIP的第3.2.1节“安全构造函数实践”中,警告不要将this
从构造函数泄漏到另一个线程,“即使发布是构造函数中的最后一个语句。”最后一部分对我来说似乎太强大了,并没有任何理由。施工后发生了什么,我必须小心避免?有例外吗?我很感兴趣,因为我最近提交了一些代码,其中我做了这件事,我想决定是否有理由重新进行重构。
答案 0 :(得分:4)
任何时候都不应该从构造函数中泄漏this
,“甚至在最后一个语句中也是如此。”由于this
没有完全构建,可能会发生一些非常奇怪的事情。有关非常类似的问题,请参阅this SO answer。
答案 1 :(得分:4)
你永远不应该将this
从构造函数中传出(称为“泄漏this
”)
不应该这样做的一个原因,即使它是构造函数的最后一行,只要对当前线程的影响不受影响,就允许JVM重新排序语句。如果将this
传递给在另一个线程中运行的进程,则重新排序会导致奇怪和微妙的错误。
另一个原因是子类可能提供自己的初始化,因此在类的构造函数的最后一行可能无法完成构造。
答案 2 :(得分:4)
就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
并在构造函数退出后立即发布它之间没有区别。