在多線程編程中,建立線程可以直接繼承Thread,也可以實現Runnable介面。但是這2種方式都有一個缺陷就是:在執行完任務之後無法擷取執行結果。
如果需要擷取執行結果,就必須通過共用變數或者使用線程通訊的方式來達到效果,這樣使用起來就比較麻煩。
而自從Java 1.5開始,就提供了Callable和Future,FutureTask,通過它們可以在任務執行完畢之後得到任務執行結果,今天我們就來看看FutureTask 是如何?的。 FutureTask (jdk 1.8)
從名字上我們可以知道,FutureTask 是一個可執行檔task,同時也擁有Future 的特性,可以擷取任務執行的結果,首先我們先看看如何使用FutureTask,這裡會簡單的涉及線程池的概念。 FutureTask 使用
public class FutureTaskDemo { // 非同步任務 static class Task implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()); return 100; } } public static void main(String[] args) { Task task = new Task(); //使用FutureTask FutureTask<Integer> futureTask = new FutureTask<Integer>(task); // 建立線程池 ExecutorService executor = Executors.newCachedThreadPool(); //非同步執行任務 executor.execute(futureTask); //使用Future Future<Integer> future = executor.submit(task); try { // 阻塞,等待非同步任務執行完畢,擷取非同步任務的傳回值 System.out.println("FutureTask result:" + futureTask.get()); System.out.println("Future result:" + future.get()); } catch (Exception e) { e.printStackTrace(); } }}
在上面我們分別使用了FutureTask 和Future來擷取傳回值,表面看似乎是不一樣,實際上本質都是一樣,這裡通過submit 提交的Callable 任務,返回的Future 其實是FutureTask,這個可以在 AbstractExecutorService源碼中找到。
public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); // 封裝任務 RunnableFuture<T> ftask = newTaskFor(task); // 執行任務 execute(ftask); return ftask;}protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value);}
這個是線程池中的細節,這裡就展開了,回到我們的FutureTask 上面來。 繼承體系
通過繼承關係我們知道 FutureTask 實際上就是Runnable和Future的合體。 資料結構
/** * The run state of this task, initially NEW. The run state * transitions to a terminal state only in methods set, * setException, and cancel. During completion, state may take on * transient values of COMPLETING (while outcome is being set) or * INTERRUPTING (only while interrupting the runner to satisfy a * cancel(true)). Transitions from these intermediate to final * states use cheaper ordered/lazy writes because values are unique * and cannot be further modified. * * Possible state transitions: //任務狀態轉移 * NEW -> COMPLETING -> NORMAL //正常完成的流程 * NEW -> COMPLETING -> EXCEPTIONAL //出現異常的流程 * NEW -> CANCELLED //被取消 * NEW -> INTERRUPTING -> INTERRUPTED //被中斷 */private volatile int state; // 任務狀態private static final int NEW = 0; //初始化狀態private static final int COMPLETING = 1; private static final int NORMAL = 2;private static final int EXCEPTIONAL = 3;private static final int CANCELLED = 4;private static final int INTERRUPTING = 5; //進行中中斷private static final int INTERRUPTED = 6; // 中斷完成/** The underlying callable; nulled out after running *///Callable 任務private Callable<V> callable;/** The result to return or exception to throw from get() */// 任務執行結果private Object outcome; /** The thread running the callable; CASed during run() */// 執行線程private volatile Thread runner;/** Treiber stack of waiting threads */// 調用get() 阻塞等待的線程節點private volatile WaitNode waiters;
主要包含了任務執行狀態,Callable 任務以及任務執行結果,文檔注釋中對任務狀態進行了很詳細的描述。 FutureTask 重要方法
1、get
public V get() throws InterruptedException, ExecutionException
擷取計算的結果, 若計算沒完成, 進行阻塞等待, 直到 計算結束或線程中斷
2、帶逾時功能的get
V get (long timeout, TimeUnit unit) throwsInterruptedException, ExecutionException, TimeoutException;
擷取計算的結果, 若計算沒完成, 進行逾時等待, 如果發生逾時則拋出TimeoutException 異常。
3、isDone
boolean isDone();
返回計算是否完成 , 若任務完成則返回true。
4、awaitDone
int awaitDone(boolean timed, long nanos) throws InterruptedException
逾時等待任務完成, 傳回值是 future 的state的狀態。
5、取消任務 cancel
public boolean cancel(boolean mayInterruptIfRunning)
當任務處於未啟動狀態時,該方法將導致此任務永遠不會被執行;當任務處於已經啟動狀態時,cancle(true)將以中斷執行此任務線程的方式來嘗試停止任務,cancle(false)將不會對正在執行此任務的線程產生影響(讓正在執行的任務運行完成);當任務處於以完成狀態時,該方法將返回false.
6、任務執行完成回調
protected void done()
當任務執行完成後將會調用該方法,在FutureTask 中是空實現,如果有需要可以重寫該方法。 構造方法
1、指定Callable任務
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; //初始化狀態}
2、指定Runnable 任務及返回結果
/** * Creates a {@code FutureTask} that will, upon running, execute the * given {@code Runnable}, and arrange that {@code get} will return the * given result on successful completion. * * @param runnable the runnable task * @param result the result to return on successful completion. If * you don't need a particular result, consider using * constructions of the form: * {@code Future<?> f = new FutureTask<Void>(runnable, null)} */public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable}
run 方法
既然FutureTask 實現了Runnable 介面,那麼就需要實現其run 方法。
public void run() { // 判斷 state 是否是new, 防止任務重複執行 if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { // 調用call方法,返回結果result result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran)//如果執行成功,則設定任務運行結果 set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) //如果發生了中斷,則等待中斷過程完成 handlePossibleCancellationInterrupt(s); }}
整個run 方法不複雜,內部調用callable的call 方法,如果任務執行成功,那麼設定運行結果 設定任務運行結果
protected void set(V v) { // 更新任務狀態 if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { //設定任務結果 outcome = v; //更新任務狀態 UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state //收尾工作 finishCompletion(); }}
回調及喚醒阻塞線程
當任務還在執行時,調用get 方法會進行阻塞等待,FutureTask 內部維護了一個線程等待的鏈表,這個我們在分析get 方法的時候再來重點分析。
private void finishCompletion() { // assert state > COMPLETING; //FutureTask 內部維護了一個線程阻塞等待鏈表(調用get 方法阻塞等待結果) for (WaitNode q; (q = waiters) != null;) { if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { for (;;) { Thread t = q.thread; if (t != null) { q.thread = null; //喚醒線程 LockSupport.unpark(t); } WaitNode next = q.next; if (next == null) break; q.next = null; // unlink to help gc q = next; } break; } } // 執行回調方法 done done(); callable = null; // to reduce footprint}
get 方法
public V get() throws InterruptedException, ExecutionException { int s = state; //檢查任務狀態,如果任務還沒有執行完成,則進行等待 if (s <= COMPLETING) s = awaitDone(false, 0L); // 返回結果 return report(s);}
get() 方法中涉及到 awaitDone 方法, 將awaitDone的運行結果賦值給state, 最後report方法根據state值進行返回相應的值, 而awaitDone是整個 FutureTask 啟動並執行核心
/** * Awaits completion or aborts on interrupt or timeout. * @param timed true if use timed waits * @param nanos time to wait, if timed * @return state upon completion */private int awaitDone(boolean timed, long nanos) throws InterruptedException { final long deadline = timed ? System.nanoTime() + nanos : 0L; WaitNode q = null; boolean queued = false; for (;;) { //如果當前線程發生中斷,則從阻塞等待隊列中移除(不等待結果了) if (Thread.interrupted()) { removeWaiter(q); //拋出中斷異常 throw new InterruptedException(); } int s = state; //如果任務已經執行完成了,並且相關參數已經設定完畢 if (s > COMPLETING) { if (q != null) q.thread = null; //重設 // 返回狀態 return s; } //任務執行完成,但是部分參數還未設定完畢(參考run 方法) else if (s == COMPLETING) // cannot time out yet Thread.yield(); //讓出cpu,等待 else if (q == null) q = new WaitNode(); //建立線程等待節點 else if (!queued) //如果還沒有入隊,則進行入隊等待 queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { //如果有逾時,則進行逾時等待 nanos = deadline - System.nanoTime(); if (nanos <= 0L) { //逾時,移除等待線程,返回狀態 removeWaiter(q); return state; } // 逾時等待 LockSupport.parkNanos(this, nanos); } else LockSupport.park(this); }}
waitDone就是將當前線程加入等待隊列(WaitNode有當前Thread的Thread變數),然後用LockSupport將自己阻塞,等待逾時或者被解除阻塞後,返回狀態(可能任務執行完成,也可能沒有執行完成),如果發生了中斷,則會拋出中斷異常。 cancel 取消任務
public boolean cancel(boolean mayInterruptIfRunning) { //new 狀態包含了任務還沒執行和任務正在執行(正在執行:call方法正在運行) if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; try { // in case call to interrupt throws exception if (mayInterruptIfRunning) { //表示是否中斷正在啟動並執行任務 try { Thread t = runner; if (t != null) t.interrupt(); //中斷,並不能即時取消任務,只是設定中斷標誌位,目標線程需要檢測該標誌位才知道是否發生了中斷 } finally { // final state //更新狀態 UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); } } } finally { //完成 finishCompletion(); } return true;}
如果state不是new 那麼就退出方法,這時的任務任務是已經完成了 或是被取消了 或是被中斷了
如果state 是new 就設定state 為正在中斷 或是取消狀態 (new 狀態包含了任務正在執行的狀態),如果mayInterruptIfRunning 為true 表示允許中斷正在啟動並執行任務,則進行中斷(中斷,並不能即時取消任務,只是設定中斷標誌位,目標線程需要檢測該標誌位才知道是否發生了中斷)。 report 返回結果
根據任務的狀態,返回不同的結果
private V report(int s) throws ExecutionException { Object x = outcome; if (s == NORMAL) //如果任務正常執行完成,則返回結果 return (V)x; if (s >= CANCELLED) //拋出取消任務異常 throw new CancellationException(); throw new ExecutionException((Throwable)x);}
總結
FutureTask 內部擁有一個Callable 任務引用,實際的任務是Callable 對象,FutureTask 定義了多種狀態,通過對狀態的設定和判斷可以知道任務的執行情況。
FutureTask 內部維護了一個等待隊列(鏈表),當調用FutureTask 的get 方法時,如果需要進行阻塞等待,則會把請求線程加入到等待隊列中,當任務執行完成(正常或者異常),則會喚醒等待隊列中的線程,這樣等待線程就可以擷取任務執行結果了。