逸出:某个不应被发布的对象被发布
发布:使对象在作用域之外的代码中被使用。例如get()获得对象或者将new出的对象放到外部集合中。
《Java并发编程实战》3-2节中提到this隐式逸出,并给出了示例代码:
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
void doSomething(Event e) {
}
interface EventSource {
void registerListener(EventListener e);
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
}
这个例子会导致逸出是因为doSomething方法是ThisEscape的实例化对象的方法,在注册了监听器后即隐含了将ThisEscape实例化对象发布出去(在其他线程中,可以被EventSource的实例化对象使用),而此时,ThisEscape的构造函数并未执行完,未构造完成的对象被发布,有可能被监听器调用,这时可能会发生异常。比较浅显的例子如监听器调用触发onEvent调用doSomething时需使用ThisEscape实例对象的成员变量,而实例对象还未完成构造,成员变量还未分配空间。
书中给出的解决方法:
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
void doSomething(Event e) {
}
interface EventSource {
void registerListener(EventListener e);
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
}
即将构造与使用(发布)分离,构造函数虽然实例化了监听器SafeListener,但并未进行注册,也就不会发布。同样的为了避免逸出,如果要在构造函数中启动一个新的线程,那么不能立即执行这个线程,而是在构造函数外,通过start方法启动。