發布(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注冊監聽)。這實際上就是修改為了構造完畢->發布對象的串行執行模式,而不是之前的異步模式,這樣就不會給我們帶來線程安全性的問題。