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

Tom*_*Tom 14 java multithreading

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

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

我的理解是,在一般情况下,允许this以逃避可能导致其他线程看到您的对象不完全构建版本和违反的初始化安全保障final领域(如讨论如这里)

但是有可能安全泄漏this吗?特别是,如果你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);}
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

也就是说,即使我们已经泄漏this到潜在的恶意Executor,泄漏之前的分配str1str2 发生 -所以对象(完全构思和目的)完全构建,即使它没有按照JLS 17.5"完全初始化" .

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

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

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

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

Zho*_*gYu 5

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

当然,唯一的例外是关于final字段。写入最终字段的构造函数的退出定义了对该字段的“冻结”操作;如果this是在 之后发布的freeze,即使没有发生在边缘之前,其他线程也会读取正确初始化的字段;但如果this在 之前发布则不然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); 
}
Run Code Online (Sandbox Code Playgroud)

这里leak(this)很安全this.x

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


如果final看起来太复杂,确实如此。我的建议是——忘了它吧!永远不要依赖final字段语义来进行不安全的发布。如果您的程序正确同步,则无需担心final字段或其微妙的语义。不幸的是,当前的气候是final尽可能地推动领域发展,这给程序员带来了过度的压力。