JDK中的Future特性
在介紹Netty的ChannelFuture之前,我們先來看看JDK中的Future是如何實現的。總的來說就是任務提交的時候會使用裝飾器模式,將任務包裝成一個FutureTask。當執行器執行該Task的時候,不僅僅會執行使用者提交的任務,還會執行裝飾器新增的額外操作,例如在執行之前記錄當前執行執行緒、執行完成後將任務結果儲存在FutureTask物件內部等。
- Thread runner => 裝飾器新增的,在執行任務之前,會在物件內儲存當前執行執行緒的引用,用於中斷任務執行
- Object outcome => 任務執行結果(返回值或異常物件),任務執行完成後會將結果set到此物件的outcome,後續可通過Future的get介面取出
- Callable<V> callble => 使用者提交的實際任務
- WaitNode waiters => 用於儲存等待執行緒,任務完成後會喚醒這些執行緒
詳細請看本人過去整理的隨筆:
Netty中的ChannelFuture
ChannelFuture是在Future基礎上的完善,它支援新增監聽器,在任務完成後自動執行相關操作。
這個其實就是觀察者模式,個人認為就是在前面的C部分新增監聽的方法呼叫,即執行執行緒執行完成後,如果發現該任務上有監聽器,則該執行執行緒還會呼叫監聽器介面。
話不多說,我們來看看它程式碼到底是怎麼寫的,跟Future的實現有何異同。相關實現關鍵內容在DefaultPromise類中,相對於前面的FutureTask。
1.物件屬性
private volatile Object result; private final EventExecutor executor; private Object listeners; private short waiters; private boolean notifyingListeners;
從物件屬性中我們可以得知,該物件沒有儲存使用者任務。實際上個人任務這是它跟Future最大的不同,Future所引用的本質上是一個任務,而ChannelFuture所引用的只有任務狀態和任務結果。所以這個DefaultPromise物件不會被作為任務提交到執行器中。
2.任務完成時的執行緒喚醒與監聽器觸發
private boolean setValue0(Object objResult) { if (RESULT_UPDATER.compareAndSet(this, null, objResult) || RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) { if (checkNotifyWaiters()) { notifyListeners(); } return true; } return false; }
當任務執行完成,通過setValue將值傳入DefaultPromise物件時,喚醒等待的執行緒,並觸發監聽器。
2.執行緒的等待與喚醒方式
public Promise<V> await() throws InterruptedException { if (isDone()) { return this; } if (Thread.interrupted()) { throw new InterruptedException(toString()); } checkDeadLock(); synchronized (this) { while (!isDone()) { incWaiters(); try { wait(); } finally { decWaiters(); } } } return this; } private synchronized boolean checkNotifyWaiters() { if (waiters > 0) { notifyAll(); } return listeners != null; }
從上述程式碼可知,這裡的執行緒等待與喚醒方式使用的內建的wait()和notify()方法,而FutureTask的等待佇列是單獨實現的。
注:這裡尚不清楚這兩種實現方式之間的優劣。
3.設定監聽器
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) { checkNotNull(listener, "listener"); synchronized (this) { addListener0(listener); } //新增監聽器時,如果發現已經完成,則直接呼叫觸發監聽器 if (isDone()) { notifyListeners(); } return this; }
如果任務已經完成,則直接觸發監聽器。防止出現"呼叫setLisener的時候,任務已經完成,導致監聽器不被觸發"。
4.任務取消
public boolean cancel(boolean mayInterruptIfRunning) { if (RESULT_UPDATER.compareAndSet(this, null, CANCELLATION_CAUSE_HOLDER)) { if (checkNotifyWaiters()) { notifyListeners(); } return true; } return false; }
從程式碼可以看出,它的實現與FutureTask有所不同,它並不會嘗試呼叫執行執行緒的interrupt()方法來中斷執行緒,只是將等待執行緒喚醒,並觸發監聽器。所以這個“取消”操作並不會影響程式碼的實際執行。
事實上“中斷”也只是一種協作方式,它只是設定中斷狀態並將執行緒喚醒(如果該執行緒正處於掛起狀態),如果使用者程式碼中沒有對中斷狀態進行判斷,也沒有使用wait()、sleep()等方法,中斷操作也是不會實際“打斷”程式碼執行的。
詳細可看:【雜談】執行緒中斷——Interrupt