目录
发布与逸出
发布(publish) 一个对象是指,使对象能够在当前作用域之外的代码中使用。当某个不应该发布的对象被发布时,这种情况就被称为 逸出(escape)。
发布一个对象
public static Set<Secret> knownSecrets;//公有变量
public void initialize(){
knownSecrets = new HashSet<Secrets>();
}
上述代码在initialize方法中实例化了一个HashSet对象,并将其引用保存到knownSecrets中发布了该对象(KnownSecrets被声明为公有变量,当前作用域之外的代码可以访问该对象),并且,如果将一个Secret对象添加到该集合(KnownSecrets是一个集合对象),那么同样会发布这个对象,因为任何代码都可以遍历这个集合。
内部可变状态逸出
Class UnsafeStates{
private String[] states = new String[]{
"AK","AL"……
};
public String[] getStates(){ return states};
}
上述方式发布states,使得任何调用者都可以修改这个数组的内容,因为在get方法中返回了states的引用,数组states已经逸出了它所在的作用域,这个私有变量被发布了。
this引用逸出
public class ThisEscape{
public ThisEscape(EventSource source){
source.registerListener(
new EventListener(){//在new这里,就新建了一个内部类的对象,持有ThisEscape类的引用
public void onEvent(Event e){
doSomething(e);
}
}
);
}
}
上述代码在发布EventListener时,也隐含的发布了ThisEscape实例本身,因为EventListener是一个非静态内部类,一个非静态内部类在编译完成后会隐含的保存一个它外围类的引用“ThisEacape.this”,然而在上述代码中,构造函数还没有完成,也就是说,ThisEscape本身还没有构造好,但是其发布的对象就已经持有了一个ThisEscape的引用。
在构造过程中使this引用逸出的一个常见的错误是,在构造函数中启动一个线程。当对象在其构造函数中创建一个线程时,无论是显式创建还是隐式创建,this引用都会被新创建的线程共享,在对象没有完全构造之前,新的线程就可以看见它。
如果想要避免不正确的构造过程,可以采用工厂方法来防止this引用在构造过程中逸出。
public class SafeListener(){
private final EventListener listener;
private SafeListener(){
listener = new EventListener(){
public void onEvent(Event e){
doSonmething(e);
}
};
}
public static SafeListener newInstance(EventSource source){
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
关于发布
如果一个已经发布的对象,能够通过非私有的变量引用和方法调用到达其他的对象,那么这些对象也都会被发布。无论其他线程会对已发布的引用执行何种操作,其实都不重要,因为误用该引用的风险始终存在。