Java:通过_happens-before_关系安全地在构造函数中“泄漏”最终类的引用?

Goetz的“Java Concurrency in Practice”第3.2.1节包含以下规则:

在施工期间不要让this参考物逃逸

我理解,一般来说,允许this转义会导致其他线程看到对象的不完整构造版本并违反final字段的初始化安全保证(如此处所讨论的)

但是有可能安全泄漏吗? 特别是,如果你在泄漏happen-before建立了先happen-before关系?

例如, 官方执行官Javadoc说

在将Runnable对象提交给Executor之前,线程中的操作发生在执行开始之前 ,可能在另一个线程中

我对Java内存模型的天真阅读理解是,类似下面的内容应该是安全的,即使它在构造函数结束之前泄漏了this

 public final class Foo { private final String str1; private String str2; public Foo(Executor ex) { str1 = "I'm final"; str2 = "I'm not"; ex.execute(new Runnable() { // Oops: Leakage! public void run() { System.out.println(str1 + str2);} }); } } 

也就是说,即使我们this泄露给潜在的恶意Executorstr1str2的赋值也会在泄漏之前发生 ,因此对象(完全构思和目的)完全构建,即使它尚未“完全初始化” “根据JLS 17.5。

请注意,我还要求该类是final ,因为任何子类的字段都会在泄漏后初始化。

我在这里错过了什么吗? 这实际上保证是否表现良好? 在我看来,它是“同步捎带”的合法例子(16.1.4)总的来说,我非常感谢任何涉及这些问题的额外资源的指针。

编辑 :我知道,正如@jtahlborn所说,我可以通过使用公共静态工厂来避免这个问题。 我正在寻找这个问题的答案,以巩固我对Java内存模型的理解。

编辑#2 : 这个答案暗示了我想要达到的目标。 也就是说,遵循其中引用的JLS规则足以保证所有final字段的可见性。 但这是否必要,或者我们是否可以利用其他先发生机制来确保我们自己的可见性保证?

你是对的。 通常 ,Java内存模型不会以任何特殊方式处理构造函数。 在构造函数退出之前或之后发布对象引用几乎没有什么区别。

当然,唯一的例外是关于final字段。 编写最终字段的构造函数的退出定义了字段上的“冻结”操作; 如果this是在freeze后发布的,即使没有发生 – 之前的边缘,其他线程将读取正确初始化的字段; 但如果在freeze之前发布,则不会。

有趣的是,如果有构造函数链接,则freeze定义在最小范围内; 例如

 -- class Bar final int x; Bar(int x, int ignore) { this.x = x; // assign to final } // [f] freeze action on this.x public Bar(int x) { this(x, 0); // [f] is reached! leak(this); } 

这里leak(this)是安全的。

有关final字段的详细信息,请参阅我的其他答案 。


如果final看起来太复杂了,那就是。 我的建议是 – 忘了! 不要依赖final字段语义来不安全地发布。 如果程序正确同步,则无需担心final字段或其精细语义。 不幸的是,目前的气候是尽可能地推动final领域,给程序员带来不必要的压力。