Goetz的Java Concurrency in Practice,第41页,提到this
引用在构造过程中如何逃脱。 “不要这样做”的例子:
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
此处this
通过doSomething(e)
引用封闭的ThisEscape
实例这一事实“逃避”。可以通过使用静态工厂方法(首先构造普通对象,然后注册侦听器)而不是公共构造函数(完成所有工作)来解决这种情况。这本书继续:
从构造函数中发布对象可以发布未完全构造的对象。即使发布是构造函数中的最后一个语句,也是如此。如果
this
引用在构造期间转义,则该对象被认为未正确构造。 < / p>
我不太明白。如果发布是构造函数中的最后一个语句,那么之前没有完成所有构建工作吗?那时this
怎么没有效?显然在那之后会发生一些伏都教,但是什么呢?
答案 0 :(得分:15)
就最终字段而言,构造函数的结尾在并发性方面是一个特殊的位置。来自Java语言规范的section 17.5:
一个对象被认为是 它完全初始化了 构造函数完成。一个线程 只能看到对象的引用 在那个对象完全之后 初始化保证看到 正确初始化的值 对象的最终字段。
最终字段的使用模型是a 简单的一个。设置最终字段 该对象中的一个对象 构造函数。不要写参考 对于正在构建的对象 另一个线程可以看到它的地方 在对象的构造函数之前 完了。如果这样,那么 当对象被另一个人看到时 线程,该线程将始终看到 正确构造的版本 该对象的最终字段。它会 还可以看到任何对象的版本或 这些最终字段引用的数组 至少与最新的一样 最后的字段是。
换句话说,如果检测到另一个线程中的对象,那么您的侦听器最终可能会看到具有默认值的最终字段。如果在构造函数完成后发生了侦听器注册,则不会发生这种情况。
就发生的事情而言,我怀疑在构造函数的最后有一个隐含的内存障碍,确保所有线程“看到”新数据;没有应用内存屏障,可能会出现问题。
答案 1 :(得分:6)
当您继承ThisEscape时,会出现另一个问题,并且子类会调用此consructor。 EventListener中的隐式this引用将具有未完全构造的对象。
答案 2 :(得分:2)
registerListener结尾和返回的构造函数之间有一个很短但有限的时间。另一个线程可以使用当时进入并尝试调用doSomething()。如果运行时此时没有直接返回到您的代码,则该对象可能处于无效状态。
我真的不确定java,但我能想到的一个例子是运行时可能会在返回给你之前重新定位实例。
我给你的机会很小。