《java并发编程实战》中介绍了“发布与逸出”,首先介绍下发布,然后重点介绍this引用逸出。
一、发布
“发布”一个对象是指,使对象能够在当前作用域之外的代码中使用。
例如:
①将一个指向该对象的引用保存到其他代码可以访问的地方
②在某一个非私有的方法中返回该引用
③将引用传递到其他类的方法中
下面我们来看几个具体的例子:
①将对象的引用保存到一个公有的静态变量中,可以使任何类和线程都能看到该对象
import java.util.*;
/**
* Secrets
*
* Publishing an object
*
* @author Brian Goetz and Tim Peierls
*/
class Secrets {
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}
}
class Secret {
}
如上面的代码所示,initialize方法中实例化一个新的HashSet对象,并将该对象的引用保存到knownSecrets中以发布对象。怎么理解呢?如果将一个Secret对象添加到集合knownSecets中,那么久会发布这个对象,因为任何代码都可以遍历这个集合,并获得这个新的Secret对象的引用。
②从非私有方法中返回一个引用,同样会发布返回的对象
/**
* UnsafeStates
* <p/>
* Allowing internal mutable state to escape
*
* @author Brian Goetz and Tim Peierls
*/
class UnsafeStates {
private String[] states = new String[]{
"AK", "AL" /*...*/
};
public String[] getStates() {
return states;
}
}
上面的代码数组states已经逸出了它所在的作用域,因为这个本应该是私有的变量已经被发布了。
③把一个对象传递给某个外部方法时,就相当于发布了这个变量。
解释一下“外部方法”,一个class C,对于C来说,“外部方法”指行为并不完全由C来规定的方法,包括其他类中定义的方法以及类C中可以被改写的方法(既不是私有方法【private】也不是终结方法【final】)
④发布一个内部类,内部类会自动持有其外部类的 this引用
二、逸出
逸出(Escape)是指 某个不应该被发布的对象被发布了。
this引用逸出指 对象还没有构造完成,this引用就被发布出去。会涉及到线程安全的问题,因为其他线程可能会通过这个逸出的引用访问到还未初始化完成的不完整对象,而其他对象可能访问到是初始化完成的对象,这就会出现不一致性。
下面我们看书中提供的一个例子:
/**
* ThisEscape
* <p/>
* Implicitly allowing the this reference to escape
*
* @author Brian Goetz and Tim Peierls
*/
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 {
}
}
如ThisEscape所示,当ThisEscape发布EventListener时,也隐含发布了ThisEscape实例本身,因为这个内部类的实例中包含了对ThisEscape实例的隐含引用。当从对象的构造函数中发布对象时,只是发布了一个尚未构造完成的对象,即使发布对象的语句位于构造函数的最后一行也是如此。
书中还提到一种this引用逸出的情况是,在构造函数中启动一个线程。当对象在其构造函数中创建了一个线程时,无论显示创建(通过将它传给构造函数)还是隐式创建(由于Thread或Runnable是该对象的一个内部类),this引用都会被新创建的线程共享。在对象尚未完全构造之前,新的线程就可以看到它。
总结一下this引用逸出的原因:
第一种情况:内部类
①在构造函数中创建了内部类
②在构造函数总把内部类发布出去
解决办法:
避免①②同时出现,等构造函数构建完成,在构造函数外面发布内部类,书中给出了正确的例子:
通过私有的构造器+公共的工程方法
/**
* SafeListener
* <p/>
* Using a factory method to prevent the this reference from escaping during construction
*
* @author Brian Goetz and Tim Peierls
*/
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 {
}
}
第二种情况:在构造函数内启动线程
①构造函数,创建线程
②构造函数中启动线程
解决办法:
构造函数内不启动线程,等构造函数构建完成后,在外面启动线程