Goetz的“实践中的Java并发”第3.2.1节包含以下规则:
在构造期间不允许
this
引用转义
我理解,一般来说,允许this
转义会导致其他线程看到对象的不完整构造版本,并违反final
字段的初始化安全保证(如所讨论的{{3 }})
但是有可能安全地泄漏this
吗?特别是,如果您在泄漏之前建立happen-before
关系?
例如,here说
在向
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
泄露给潜在的恶意Executor
,str1
和str2
的分配也会发生在之前泄漏,所以对象(完全构思和目的)完全构造,即使它没有按照JLS 17.5“完全初始化”。
请注意,我还要求类为final
,因为任何子类的字段都会在泄漏后初始化。
我在这里遗漏了什么吗?这实际上是保证是否表现良好?在我看来,它是“同步捎带”的合法例子(16.1.4)。总的来说,我非常感谢任何涉及这些问题的额外资源的指针。
编辑:我知道,正如@jtahlborn所说,我可以通过使用公共静态工厂来避免这个问题。我正在寻找这个问题的答案,以巩固我对Java内存模型的理解。
编辑#2 :official Executor Javadoc暗示了我想要达到的目标。也就是说,遵循其中引用的JLS规则足够,以保证所有final
字段的可见性。但这是否必要,或者我们是否可以利用其他发生在之前的机制来确保我们自己的可见性保证?
答案 0 :(得分:4)
你是对的。在 general 中,Java内存模型不以任何特殊方式处理构造函数。在构造函数退出之前或之后发布对象引用几乎没有什么区别。
唯一的例外是关于final
字段。编写最终字段的构造函数的退出定义了字段上的“冻结”操作;如果在this
之后发布freeze
,即使没有发生 - 之前的边缘,其他线程将读取正确初始化的字段;但如果在this
之前发布freeze
,则不会。
有趣的是,如果存在构造函数链接,则在最小范围上定义freeze
; e.g。
-- 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)
是安全的w.r.t. this.x
。
有关final
字段的详细信息,请参阅我的其他answer。
如果final
似乎过于复杂,那就是。我的建议是 - 忘了!不要依赖final
字段语义来不安全地发布。如果程序正确同步,则无需担心final
字段或其精细语义。不幸的是,目前的气候是尽可能地推动final
字段,给程序员带来不必要的压力。