发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系。
什么是发布?简单来说就是提供一个对象的引用给作用域之外的代码。比如return一个对象,或者作为参数传递到其他类的方法中。
什么是逸出?如果一个类还没有构造结束就已经提供给了外部代码一个对象引用即发布了该对象,此时叫做对象逸出,对象的逸出会破坏线程的安全性。
概念我们知道了,可我们要关注什么地方呢?我们要关注的时候就是逸出问题,在不该发布该对象的地方就不要发布该对象,例如以下代码:
1 class UnsafeStates{
2 private String[] states = new String[]{"AK", "AL"};
3
4 public String[] getStates(){
5 return states;
6 }
7 }
states变量作用域是private而我们在getStates方法中却把它发布了,这样就称为数组states逸出了它所在的作用域。
然而更加隐蔽和需要我们注意的是this逸出,这个问题要引起重点关注。什么是this逸出?观察以下代码:
1 public class ThisEscape{
2 private int value;
3 public ThisEscape(EventSource source){
4 source.registerListener{
5 new EventListener(){
6 public void onEvent(Event e){
7 doSomething(e);
8 }
9 }
10 }
11 //一些初始化工作
12 value = 7;
13 }
14
15 public void doSomething(Event e){
16 System.out.println(value);
17 }
18
19 }
在构造方法中我们定义了一个匿名内部类,匿名内部类是一个事件监听类,当事件监听类注册完毕后,实际上我们已经将EventListener匿名内部类发布出去了,而此时我们实际上已经携带了this逸出,重点在于这个时候我们还有一些初始化工作没有做完(代码11行之后),这也就是上面所说的,一个类还没有构造结束我们已经将发布了。那怎么来避免this逸出呢?既然我们没有构造完构造函数,那我们就将构造函数构造完嘛,将构造函数定义为private作用域。如以下代码所示:
1 public class SafeListener{
2 private final EventListener listener;
3
4 private safeListener(){
5 listener = new EventListener(){
6 public void onEvent(Event e){
7 doSomething(e);
8 }
9 }
10 }
11
12 public static SafeListener newInstance(EventSource source){
13 SafeListener safeListener = new SafeListener();
14 safeListener.registerListener(safeListener.listener);
15
16 return safeListener;
17 }
18 }
我们首先将构造函数设定为private,其次我们在构造函数未完成时不将对象进行发布,而是使用工厂方法,在工厂方法newInstance中待构造函数执行完毕后再将对象进行发布(代码中即为registenerListener注册监听)。这实际上就是修改为了构造完毕->发布对象的串行执行模式,而不是之前的异步模式,这样就不会给我们带来线程安全性的问题。