系列目錄:
- 揭開Future的神祕面紗——任務取消
- 揭開Future的神祕面紗——任務執行
- 揭開Future的神祕面紗——結果獲取
使用案例
在之前寫過的一篇隨筆中已經提到了Future的應用場景和特性。(ExecutorService——<T> Future<T> submit(Callable<T> task))
我們先來回顧一下:
public class FutureCancelDemo { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); Future<Target> future = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); //給足時間讓啟動起來,但又不足以讓其完成 future.cancel(true); } } class Target { //任務目標 } class DemoTask implements Callable<Target> { //任務 private static int counter = 0; private final int id = counter++; @Override public Target call() throws Exception { System.out.println(this+ " start..."); TimeUnit.SECONDS.sleep(5); //模擬任務執行需要的時間 System.out.println(this + " completed!"); return new Target(); } @Override public String toString() { return "Task[" + id + "]"; } }
一般情況下,我們會在哪裡用到Future物件呢?
就是當我們需要控制任務(Runnable/Callable物件)的時候,我們把任務提交給執行器(ExecutorService.submit()),並返回一個控制控制程式碼(Future)。以便在未來的某個時刻檢查任務執行狀態、獲取任務執行結果、以及在必要的時候取消任務等。
今天我們就來看看,Future是如何取消任務的。
任務取消做了什麼
我們知道Future只是一個介面,它到底是如何實現任務取消的呢?
我們知道,把任務提交給執行器,執行器返回給我們一個Future。但是由於程式碼封裝得很好,Future和ExecutorService都只是一個介面,我們只知道怎麼用,卻不知道其內部是如何實現的。如果要檢視它到底是如何實現的就要追根溯源的查,直到找到最終的實現類。首先,我們的ExecutorService是用工廠類Executors獲得的。就拿上述程式碼為例,我們獲得了一個緩衝執行緒池ThreadPoolExecutor型別的物件,而ThreadPoolExecutor並沒有重寫submit方法,而是延用它父類的實現,而它父類便是AbstractExecutorSevice。這個類提供了ExecutorService介面最基本的底層實現。我們終於找到了submit方法的實現。
從這裡我們可以看到,該方法以RunnableFuture作為返回值。且該值由newTaskFor方法生成。
然而這RunnableFuture,FutureTask,Future到底是何關係呢?
即RunnableFuture繼承了Runnable及Future介面,表示一個可控制的任務。而FutureTask實現了這個介面。故Future的取消操作最終由這個FutureTask實現。
我們來看看它是如何實現取消操作(future.cancel())的。
由上我們知道,取消操作與一個變數mayInterruptIfRunning有很大關聯。由以上程式碼可知,
mayInterruptIfRunning為true時
(1)設定任務狀態為INTERRUPTING
(2)中斷執行任務的執行緒
(3)若中斷成功,設定任務狀態為INTERRUPTED
(4)執行finishCompletion() => 此方法與等待執行緒有關。請看結果獲取。
mayInterruptIfRunning為false時
(1)設定任務狀態為CANCELLED
(2)執行finishCompletion() 同上
我們來看看cancel方法的定義。
說的就是,如果任務已經開始被執行緒執行,那麼mayInterruptIfRunning決定的是,在任務執行過程中,要不要通過中斷其執行執行緒來嘗試打斷任務。
任務取消≠任務不再執行
熟悉執行緒中斷的都知道,它是一種協作機制,如果任務程式碼中沒有對中斷訊號進行響應,那麼程式就會繼續執行下去。這是中斷的情況,不中斷自然肯定不能打斷任務的執行。
那麼問題就來了,這個取消操作到底有什麼意義呢?
意義就是,如果任務已被標識為已取消,那麼獲取其結果的執行緒,就不需要等待其完成,丟擲已取消異常。事實上任務其實還在跑。
通過一個例子加深一下印象:
public class FutureCancelDemo2 { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); //緩衝執行緒池 Future<Target> future = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); //給足時間讓啟動起來,但又不足以讓其完成 boolean cancelResult1 = future.cancel(true); //true表示,如果已經執行,則中斷 Future<Target> future2 = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); boolean cancelResult2 = future2.cancel(false); System.out.println("cancelResult1:" + cancelResult1); System.out.println("cancelResult2:" + cancelResult2); try { Target target = future2.get(); } catch (ExecutionException e) { e.printStackTrace(); } } }
執行結果:
很明顯,程式中,在任務2(Task[1])在其執行完成之前,已經被我們"取消"了。但是從控制檯輸出可知,它其實還在跑,並在完成的時候輸出"Task[1] completed!"。然後我們嘗試獲取任務2(Task[1])的結果,發現其丟擲了已取消異常。
cancel方法返回值的含義
一切都跟前面符合前面的說明,但是細心的朋友可能發現一件奇怪的事情。那就是為什麼不管是cancel(false)還是cancel(true)返回值都是true?
我們來看看官方的定義:
也就是說,cancel的返回值如果是false,則情況有這幾種:在此操作之前,任務已經完成,在這之前已經呼叫過一次cancel,其他原因導致無法取消。
其他任何情況都會返回true