阅读以下代码是“不安全构造”的示例,因为它允许此引用转义。我不太明白“这个”是如何逃脱的。我对 Java 世界很陌生。任何人都可以帮助我理解这一点。
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
您在问题中发布的示例来自Brian Goetz 等人的“Java Concurrency In Practice”。它在第 3.2 节“发布和逃逸”中。我不会尝试在此处重现该部分的详细信息。(去为你的书架买一本,或者从你的同事那里借一本!)
示例代码说明的问题是构造函数允许对正在构造的对象的引用在构造函数完成创建对象之前“转义”。这是一个问题,原因有两个:
如果引用转义,则某些对象可以在其构造函数完成初始化之前使用该对象,并看到它处于不一致(部分初始化)状态。即使对象在初始化完成后转义,声明子类也会导致违反这一点。
根据JLS 17.5,可以安全地使用对象的最终属性而无需同步。但是,这仅在对象引用在其构造函数完成之前未发布(不转义)时才成立。如果你打破这个规则,结果是一个潜在的并发错误,当代码在多核/多处理器机器上执行时,它可能会咬你。
这个
ThisEscape
例子是偷偷摸摸的,因为引用是this
通过隐式传递给匿名EventListener
类构造函数的引用进行转义的。但是,如果过早明确地发布参考文献,也会出现同样的问题。下面是一个例子来说明对象初始化不完全的问题:
public class Thing { public Thing (Leaker leaker) { leaker.leak(this); } } public class NamedThing extends Thing { private String name; public NamedThing (Leaker leaker, String name) { super(leaker); } public String getName() { return name; } }
如果该
Leaker.leak(...)
方法调用getName()
泄漏的对象,它将得到null
... 因为在那个时间点对象的构造函数链还没有完成。这里有一个例子来说明
final
属性的不安全发布问题。public class Unsafe { public final int foo = 42; public Unsafe(Unsafe[] leak) { leak[0] = this; // Unsafe publication // Make the "window of vulnerability" large for (long l = 0; l < /* very large */ ; l++) { ... } } } public class Main { public static void main(String[] args) { final Unsafe[] leak = new Unsafe[1]; new Thread(new Runnable() { public void run() { Thread.yield(); // (or sleep for a bit) new Unsafe(leak); } }).start(); while (true) { if (leak[0] != null) { if (leak[0].foo == 42) { System.err.println("OK"); } else { System.err.println("OUCH!"); } System.exit(0); } } } }
此应用程序的某些运行可能会打印“哎哟!” 而不是“OK”,表明
Unsafe
由于通过leak
数组不安全的发布,主线程已经观察到处于“不可能”状态的对象。这是否发生将取决于您的 JVM 和您的硬件平台。现在这个例子显然是人为的,但不难想象这种事情在真正的多线程应用中是如何发生的。
作为 JSR 133 的结果,当前的 Java 内存模型在 Java 5(JLS 的第 3 版)中指定。在此之前,Java 与内存相关的方面没有详细说明。引用早期版本/版本的来源已过时,但 Goetz 版本 1 中有关内存模型的信息是最新的。
内存模型的一些技术方面显然需要修改;请参阅https://openjdk.java.net/jeps/188和https://www.infoq.com/articles/The-OpenJDK9-Revised-Java-Memory-Model/。但是,这项工作尚未出现在 JLS 修订版中。