1. 程式人生 > >FutureTask源碼解析

FutureTask源碼解析

shutdown turn 轉換 void 異常 final pin down images

在Java中一般通過繼承Thread類或者實現Runnable接口這兩種方式來創建多線程,但是這兩種方式都有個缺陷,就是不能在執行完成後獲取執行的結果,因此Java 1.5之後提供了CallableFuture接口,通過它們就可以在任務執行完畢之後得到任務的執行結果。

Callable接口

public interface Callable<V> {
    V call() throws Exception;
}

可以看到Callable是個泛型接口,泛型V就是要call()方法返回的類型。Callable接口和Runnable接口很像,都可以被另外一個線程執行,但是正如前面所說的,Runnable不會返回數據也不能拋出異常。

Future接口

Future接口代表異步計算的結果,通過Future接口提供的方法可以查看異步計算是否執行完成,或者等待執行結果並獲取執行結果,同時還可以取消執行。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) 
throws InterruptedException, ExecutionException, TimeoutException; }
  • cancel():cancel()方法用來取消異步任務的執行。如果異步任務已經完成或者已經被取消,或者由於某些原因不能取消,則會返回false。如果任務還沒有被執行,則會返回true並且異步任務不會被執行。如果任務已經開始執行了但是還沒有執行完成,若mayInterruptIfRunning為true,則會立即中斷執行任務的線程並返回true,若mayInterruptIfRunning為false,則會返回true且不會中斷任務執行線程。

  • isCanceled():判斷任務是否被取消,如果任務在結束(正常執行結束或者執行異常結束)前被取消則返回true,否則返回false。

  • isDone():判斷任務是否已經完成,如果完成則返回true,否則返回false。需要註意的是:任務執行過程中發生異常、任務被取消也屬於任務已完成,也會返回true。

  • get():獲取任務執行結果,如果任務還沒完成則會阻塞等待直到任務執行完成。如果任務被取消則會拋出CancellationException異常,如果任務執行過程發生異常則會拋出ExecutionException異常,如果阻塞等待過程中被中斷則會拋出InterruptedException異常。

  • get(long timeout,Timeunit unit):帶超時時間的get()版本,如果阻塞等待過程中超時則會拋出TimeoutException異常。

FutureTask

Future只是一個接口,不能直接用來創建對象,FutureTask是Future的實現類

FutureTask實現了RunnableFuture接口,則RunnableFuture接口繼承了Runnable接口和Future接口,所以FutureTask既能當做一個Runnable直接被Thread執行,也能作為Future用來得到Callable的計算結果。

使用方式

FutureTask一般配合ExecutorService來使用,也可以直接通過Thread來使用。

public class CallDemo {
    // 1. 繼承Callable接口,實現call()方法,泛型參數為要返回的類型
    // 100累加
    static class Task  implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("Thread [" + Thread.currentThread().getName() + "] is running");
            int result = 0;
            for(int i = 0; i < 100;++i) {
                result += i;
            }
            Thread.sleep(3000);
            return result;
        }
    } 

    public static void main(String[] args) throws ExecutionException, InterruptedException {
         // 第一種方式:Future + ExecutorService
         Task task = new Task();
         ExecutorService service = Executors.newCachedThreadPool();
         Future<Integer> future = service.submit(task1);
         service.shutdown();//優雅關閉
 
         // 第二種方式: FutureTask + ExecutorService
         ExecutorService service = Executors.newCachedThreadPool();
         FutureTask<Integer> futureTask = new FutureTask<Integer>(new Task());
         service.submit(futureTask);
         service.shutdown();
 
         // 第三種方式:FutureTask + Thread
         // 新建FutureTask,需要一個實現了Callable接口的類的實例作為構造函數參數
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Task());
        // 新建Thread對象並啟動
        Thread thread = new Thread(futureTask);
        thread.setName("Task thread");
        thread.start();
        //註釋掉其他兩種 留一種方式
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        System.out.println("Thread [" + Thread.currentThread().getName() + "] is running");
 
        // 調用isDone()判斷任務是否結束
        if(!futureTask.isDone()) {
            System.out.println("Task is not done");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        int result = 0;
        try {
            // 調用get()方法獲取任務結果,如果任務沒有執行完成則阻塞等待
            result = futureTask.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
         System.out.println("result is " + result);
     }
}

源碼分析之構造函數

FutureTask有兩個構造函數,其中一個如下:

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
}

這個構造函數會把傳入的Callable變量保存在this.callable字段中,該字段定義為private Callable<V> callable;用來保存底層的調用,在被執行完成以後會指向null,接著會初始化state字段為NEW。

state字段用來保存FutureTask內部的任務執行狀態,一共有7中狀態,每種狀態及其對應的值如下:

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;

其中需要註意的是state是volatile類型的,也就是說只要有任何一個線程修改了這個變量,那麽其他所有的線程都會知道最新的值。

  • NEW:表示是個新的任務或者還沒被執行完的任務。這是初始狀態。

  • COMPLETING:任務已經執行完成或者執行任務的時候發生異常,但是任務執行結果或者異常原因還沒有保存到outcome字段(outcome字段用來保存任務執行結果,如果發生異常,則用來保存異常原因)的時候,狀態會從NEW變更到COMPLETING。但是這個狀態會時間會比較短,屬於中間狀態。

  • NORMAL:任務已經執行完成並且任務執行結果已經保存到outcome字段,狀態會從COMPLETING轉換到NORMAL。這是一個最終態。

  • EXCEPTIONAL:任務執行發生異常並且異常原因已經保存到outcome字段中後,狀態會從COMPLETING轉換到EXCEPTIONAL。這是一個最終態。

  • CANCELLED:任務還沒開始執行或者已經開始執行但是還沒有執行完成的時候,用戶調用了cancel(false)方法取消任務且不中斷任務執行線程,這個時候狀態會從NEW轉化為CANCELLED狀態。這是一個最終態。

  • INTERRUPTING: 任務還沒開始執行或者已經執行但是還沒有執行完成的時候,用戶調用了cancel(true)方法取消任務並且要中斷任務執行線程但是還沒有中斷任務執行線程之前,狀態會從NEW轉化為INTERRUPTING。這是一個中間狀態。

  • INTERRUPTED:調用interrupt()中斷任務執行線程之後狀態會從INTERRUPTING轉換到INTERRUPTED。這是一個最終態。

ps:所有值大於COMPLETING的狀態都表示任務已經執行完成(任務正常執行完成,任務執行異常或者任務被取消)。

各個狀態之間的可能轉換關系如下圖所示:

技術分享

另外一個構造函數如下:

public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
}

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}

static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

這個構造函數會把傳入的Runnable封裝成一個Callable對象保存在callable字段中,同時如果任務執行成功的話就會返回傳入的result。這種情況下如果不需要返回值的話可以傳入一個null。

在new了一個FutureTask對象之後,接下來就是在另一個線程中執行這個Task,無論是通過直接new一個Thread還是通過線程池,執行的都是run()方法,接下來就看看run()方法的實現。

源碼分析之run方法

run()方法實現如下:

public void run() {
    // 狀態如果不是NEW,說明任務或者已經執行過,或者已經被取消,直接返回
    // 狀態如果是NEW,則嘗試把當前執行線程保存在runner字段(runnerOffset)中,如果賦值失敗則直接返回
    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 {
                // 執行任務  計算邏輯
                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()方法首先會

  1. 判斷當前任務的state是否等於NEW,如果不為NEW則說明任務或者已經執行過,或者已經被取消,直接返回。
  2. 如果狀態為NEW則接著會通過unsafe類把任務執行線程引用CAS的保存在runner字段中,如果保存失敗,則直接返回。
  3. 執行任務。
  4. 如果任務執行發生異常,則調用setException()方法保存異常信息。否則執行set()設置結果和狀態值。
  5. 任務如果是被中斷的,執行 handlePossibleCancellationInterrupt()處理狀態和中斷響應。

源碼分析之setException方法

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

在setException()方法中

  1. 首先會CAS的把當前的狀態從NEW變更為COMPLETING(中間狀態)狀態。
  2. 把異常原因保存在outcome字段中,outcome字段用來保存任務執行結果或者異常原因。
  3. CAS的把當前任務狀態從COMPLETING變更為EXCEPTIONAL。這個狀態轉換對應著上圖中的二。
  4. 調用finishCompletion()。

源碼分析之set方法

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}
  1. 首先會CAS的把當前的狀態從NEW變更為COMPLETING狀態。
  2. 把任務執行結果保存在outcome字段中。
  3. CAS的把當前任務狀態從COMPLETING變更為NORMAL。這個狀態轉換對應著上圖中的一。
  4. 調用finishCompletion()。

發起任務線程(threadpool)執行任務線程(main)通常情況下都不會是同一個線程,在任務執行線程執行任務的時候,任務發起線程可以查看任務執行狀態、獲取任務執行結果、取消任務等等操作,接下來分析下這些操作。

源碼分析之get方法

任務發起線程可以調用get()方法來獲取任務執行結果,如果此時任務已經執行完畢則會直接返回任務結果,如果任務還沒執行完畢,則調用方會阻塞直到任務執行結束返回結果為止。get()方法實現如下:

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}
  1. 判斷任務當前的state <= COMPLETING是否成立。前面分析過,COMPLETING狀態是任務是否執行完成的臨界狀態。
  2. 如果成立,表明任務還沒有結束(這裏的結束包括任務正常執行完畢,任務執行異常,任務被取消),則會調用awaitDone()進行阻塞等待。
  3. 如果不成立表明任務已經結束,調用report()返回結果。

當調用get()獲取任務結果但是任務還沒執行完成的時候,調用線程會調用awaitDone()方法進行阻塞等待,該方法定義如下:

private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    // 計算等待截止時間
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        // 1. 判斷阻塞線程是否被中斷,如果被中斷則在等待隊列中刪除該節點並拋出InterruptedException異常
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        // 2. 獲取當前狀態,如果狀態大於COMPLETING
        // 說明任務已經結束(要麽正常結束,要麽異常結束,要麽被取消)
        // 則把thread顯示置空,並返回結果
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        // 3. 如果狀態處於中間狀態COMPLETING
        // 表示任務已經結束但是任務執行線程還沒來得及給outcome賦值
        // 這個時候讓出執行權讓其他線程優先執行
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        // 4. 如果等待節點為空,則構造一個等待節點
        else if (q == null)
            q = new WaitNode();
        // 5. 如果還沒有入隊列,則把當前節點加入waiters首節點並替換原來waiters
        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;
            }
            // 6. 阻塞等待特定時間
            LockSupport.parkNanos(this, nanos);
        }
        else
            // 6. 阻塞等待直到被其他線程喚醒
            LockSupport.park(this);
    }
}

awaitDone()中有個死循環,每一次循環都會:

  1. 判斷調用get()的線程是否被其他線程中斷,如果是的話則在等待隊列中刪除對應節點然後拋出InterruptedException異常。
  2. 獲取任務當前狀態,如果當前任務狀態大於COMPLETING則表示任務執行完成,則把thread字段置null並返回結果。
  3. 如果任務處於COMPLETING狀態,則表示任務已經處理完成(正常執行完成或者執行出現異常),但是執行結果或者異常原因還沒有保存到outcome字段中。這個時候調用線程讓出執行權讓其他線程優先執行。
  4. 如果等待節點為空,則構造一個等待節點WaitNode。
  5. 如果第四步中新建的節點還沒如隊列,則CAS的把該節點加入waiters隊列的首節點。
  6. 阻塞等待。

假設當前state=NEW且waiters為NULL,也就是說還沒有任何一個線程調用get()獲取執行結果,這個時候有兩個線程threadA和threadB先後調用get()來獲取執行結果。再假設這兩個線程在加入阻塞隊列進行阻塞等待之前任務都沒有執行完成且threadA和threadB都沒有被中斷的情況下(因為如果threadA和threadB在進行阻塞等待結果之前任務就執行完成或線程本身被中斷的話,awaitDone()就執行結束返回了),執行過程是這樣的,以threadA為例:

  1. 第一輪for循環,執行的邏輯是q == null,所以這時候會新建一個節點q。第一輪循環結束。
  2. 第二輪for循環,執行的邏輯是!queue,這個時候會把第一輪循環中生成的節點的netx指針指向waiters,然後CAS的把節點q替換waiters。也就是把新生成的節點添加到waiters鏈表的首節點。如果替換成功,queued=true。第二輪循環結束。
  3. 第三輪for循環,進行阻塞等待。要麽阻塞特定時間,要麽一直阻塞知道被其他線程喚醒。

技術分享

源碼分析之cancel方法

public boolean cancel(boolean mayInterruptIfRunning) {
    // 1. 如果任務已經結束,則直接返回false  任務已經開始
    if (state != NEW)
        return false;
    // 2. 如果需要中斷任務執行線程
    if (mayInterruptIfRunning) {
        // 2.1. 把任務狀態從NEW轉化到INTERRUPTING
        if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING))
            return false;
        Thread t = runner;
        // 2.2. 中斷任務執行線程
        if (t != null)
            t.interrupt();
        // 2.3. 修改狀態為INTERRUPTED
        UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); // final state
    }
    // 3. 如果不需要中斷任務執行線程,則直接把狀態從NEW轉化為CANCELLED
    else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED))
        return false;
    finishCompletion();
    return true;
}
  1. 判斷任務當前執行狀態,如果任務狀態不為NEW,則說明任務或者已經執行完成,或者執行異常,不能被取消,直接返回false表示執行失敗。
  2. 判斷需要中斷任務執行線程,則
    • 把任務狀態從NEW轉化到INTERRUPTING。這是個中間狀態。
    • 中斷任務執行線程。
    • 修改任務狀態為INTERRUPTED。這個轉換過程對應上圖中的四。
  3. 如果不需要中斷任務執行線程,直接把任務狀態從NEW轉化為CANCELLED。如果轉化失敗則返回false表示取消失敗。這個轉換過程對應上圖中的四。
  4. 調用finishCompletion()。

源碼分析之finishCompletion方法

不管是任務執行異常還是任務正常執行完畢,或者取消任務,最後都會調用finishCompletion()方法

private void finishCompletion() {
    // assert state > COMPLETING;
    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();
    callable = null;        // to reduce footprint
}

依次遍歷waiters鏈表,喚醒節點中的線程,然後把callable置空。

被喚醒的線程會各自從awaitDone()方法中的LockSupport.park()阻塞中返回,然後會進行新一輪的循環。在新一輪的循環中會返回執行結果(或者更確切的說是返回任務的狀態)。

源碼分析之report()

report()方法用在get()方法中,作用是把不同的任務狀態映射成任務執行結果。

private V report(int s) throws ExecutionException {
    Object x = outcome;
    // 1. 任務正常執行完成,返回任務執行結果
    if (s == NORMAL)
        return (V)x;
    // 2. 任務被取消,拋出CancellationException異常
    if (s >= CANCELLED)
        throw new CancellationException();
    // 3. 其他狀態,拋出執行異常ExecutionException
    throw new ExecutionException((Throwable)x);
}

技術分享

如果任務處於NEW、COMPLETING和INTERRUPTING這三種狀態的時候是執行不到report()方法的,所以沒必要對這三種狀態進行轉換。

源碼分析之get(long,TimeUnit)

帶超時等待的獲取任務結果,實現如下:

public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    if (s <= COMPLETING &&
        // 如果awaitDone()超時返回之後任務還沒結束,則拋出異常
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException();
    return report(s);
}

跟get()不同點在於get(long,TimeUnit)會在awaitDone()超時返回之後拋出TimeoutException異常。

源碼分析之isCancelled()和isDone()

這兩個方法分別用來判斷任務是否被取消和任務是否執行完成,實現都比較簡單,代碼如下:

public boolean isCancelled() {
    return state >= CANCELLED;
}

public boolean isDone() {
    return state != NEW;
}

總結

FutureTask的實現還是比較簡單的,當用戶實現Callable()接口定義好任務之後,把任務交給其他線程進行執行。FutureTask內部維護一個任務狀態,任何操作都是圍繞著這個狀態進行,並隨時更新任務狀態。任務發起者調用get()獲取執行結果的時候,如果任務還沒有執行完畢,則會把自己放入阻塞隊列中然後進行阻塞等待。當任務執行完成之後,任務執行線程會依次喚醒阻塞等待的線程。調用cancel()取消任務的時候也只是簡單的修改任務狀態,如果需要中斷任務執行線程的話則調用Thread.interrupt()中斷任務執行線程。

有個值得關註的問題就是當任務還在執行的時候用戶調用cancel(true)方法能否真正讓任務停止執行呢?

當調用cancel(true)方法的時候,實際執行還是Thread.interrupt()方法,而interrupt()方法只是設置中斷標誌位,如果被中斷的任務執行線程處於sleep()、wait()或者join()邏輯中則會拋出InterruptedException異常。因此結論是:cancel(true)並不一定能夠停止正在執行的異步任務。

FutureTask源碼解析