FutureTask源碼閱讀

1.參數說明

1.任務狀態

1.新創建狀態:NEW

    private static final int NEW   = 0;

2.完成中但還沒完成:COMPLETING

    private static final int COMPLETING   = 1;

3.正常狀態:NORMAL

    private static final int NORMAL       = 2;

4.異常狀態:EXCEPTIONAL

    private static final int EXCEPTIONAL  = 3;

5.取消狀態:CANCELLED

    private static final int CANCELLED    = 4;

6.中斷中狀態:INTERRUPTING

	private static final int INTERRUPTING = 5;

7.已經中斷狀態:INTERRUPTED

private static final int INTERRUPTED  = 6;

8.狀態切換
參照給出的註釋得到下面狀態切換的幾種情況:

     /**
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */

2.等待的節點:WaiterNode

可以看到註釋這邊說的是使用Treiber Stack的方式來實現,即無鎖併發棧,他的基本思想也是通過CAS,後面我們會看到,這邊先了解一下這個數據結構,WaitNode,記錄對應的等待線程和下一個等待的節點.
在FutureTask中維護了一個WaiterNode即private volatile WaitNode waiters;
這邊的waiter其實對應的就是FutureTask的get操作,因爲任務可能還沒處理完, get得不到結果,可能多次get,所以需要對這些get操作進行阻塞,等待真正的任務處理結束之後,再回到隊列中去處理get的操作

    /**
     * Simple linked list nodes to record waiting threads in a Treiber
     * stack.  See other classes such as Phaser and SynchronousQueue
     * for more detailed explanation.
     */
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

3.返回結果或者返回異常:outcome

後面我們會具體說到這個,這邊先了解一下,可以設置返回結果(在構造函數中設置),如果一切正常的話,按照設置的結果返回,如果發生異常則會被設置爲對應的異常實例。
outcome在本地可以說算是一個緩存,當任務運行過程中,如果調用get方法,會一直等待任務結束,然後返回outcome的結果,像我們剛纔說的兩種情況
1.一切正常返回構造時設置的結果
2.發生異常則返回對應異常實例

    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes

4.任務回調接口:Callable

	/**
	 * A task that returns a result and may throw an exception.
	 * Implementors define a single method with no arguments called
	 **/

    /** The underlying callable; nulled out after running */
    private Callable<V> callable;

5.任務線程:runner

    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;

2.構造函數

1.Callable入參

通過傳入Callable的實現類來構造FutureTask

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

2.Runnable入參

參考註釋,如果不需要指定result,可以傳null。這個構造方法會把Runnable的入參包裝成Callable的具體實現類,這邊使用的是RunnableAdapter這個實現類

    /**
     * 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)}
     * @throws NullPointerException if the runnable is null
     */
    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);
    }

RunnableAdapter支持傳入result,就是我們上面說的outCome怎麼返回的問題,這邊不贅述

    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;
        }
    }

3.方法實現

1.線程執行:run

2.獲取運行結果:get

如果當前的FutureTask是NEW或者COMPLETING狀態,也就是還沒有到最終狀態的時候,需要把get操作等待在wait節點的鏈表中。
當前任務結束之後,返回結果,即report,report可能返回對應的result,也可能是拋出錯誤。

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        //線程還在NEW或者COMPLETING狀態則進行等待.
        if (s <= COMPLETING) {
            s = awaitDone(false, 0L);
        }
        //等待結束之後,返回結果
        return report(s);

    }

1.返回執行結果:report

    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL) {
            //正常狀態,返回之前設置的Result
            return (V) x;
        }
        //如果是被中斷或者取消,拋出錯誤
        if (s >= CANCELLED) {
            throw new CancellationException();
        }
        throw new ExecutionException((Throwable) x);
    }

2.等待任務執行結束:awaitDown

要看一下awaitDown方法如果等待線程執行完任務。
這邊的判斷條件比較多(好幾個else if…這代碼寫的不是很優雅啊)。我們先一步一步來看一下他的實現
1.剛開始先是根據timed是否計時來獲取最終結束時間(如果有計時,沒有計時的時候爲0)
2.然後進入死循環
        1.如果當前線程是中斷狀態,則把waiter移除隊列,等下看一下這個移除操作,不僅僅是移除自己這個節點,移出之後拋出中斷異常
        下面這些判斷都是在同一個ifelse的判斷中所以是有關係的,這邊把這些判斷按照條件成立前後關係標註A1~n來闡述能更好說明問題.
        A1.如果狀態已經不是NEW和COMPLETING了,可以返回結果了,這邊有一步是q.thread=null,這個操作是告訴後面的removeWaiter方法,這個waiter是可以移除的節點了。
        A2.如果線程還是COMPLETING,則調用Thread.yield,進入就緒態,讓出CPU重新搶佔
        A3.如果FutureTask是NEW的狀態要新創建一個wait節點
        A4.FutureTask還是NEW或者COMPLETING的狀態並且waiter還沒有進隊列,因爲不知道要等待多久,所以還是把waiter插入隊列
        A5.此時狀態還是NEW或者COMPLETING,如果有計時在實現計時的處理,時間到了移除隊列,時間沒有到則掛起對應時間等待。
        A6.最後是沒有計時的情況,休眠等待被喚醒

  private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        //通過timed來判斷是否計時和結束時間
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        //進入死循環
        for (; ; ) {
            //線程被中斷則要把當前的Wait節點移除掉
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            //線程已經是COMPLETING之後的狀態:正常、異常、中斷
            if (s > COMPLETING) {
                //判斷節點不爲空,則把節點的線程置空,返回狀態
                if (q != null) {
                    //併發各種情況都有可能,所以這邊也要清空一下Thread
                    q.thread = null;
                }
                return s;
            }// cannot time out yet
            //如果線程還是COMPLETING,則調用Thread.yield,進入就緒態,讓出CPU重新搶佔
            else if (s == COMPLETING) {
                Thread.yield();
            }
            //這邊應該是  !s > COMPLETING && q!=null,就是說FutureTask還是NEW、或者COMPLETING的狀態,誰也不知道他什麼時候執行,我先進入wait queue等待一下。
            else if (q == null) {
                q = new WaitNode();
            }
            //這邊應該是  !s >= COMPLETING && q!=null,就是說FutureTask還是NEW的狀態,誰也不直到他什麼時候執行,我先進入wait queue等待一下。
            else if (!queued) {
                //這邊的處理是  q.next = waiters.也就是說後面進來的節點會成爲FutureTask的waiter,通過這個waiter去查找後面的元素,是棧的實現
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
            }
            //這邊就是 !s >= COMPLETING && q!=null && 加入隊列,如果有計時,開始掛起計時
            else if (timed) {
                //時間到了則從節點中移除
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                //否則掛起對應時間
                LockSupport.parkNanos(this, nanos);
            } else {
                //不滿足上述情況進行掛起,當FutureTask的run運行結束之後判斷waiter的Thread不爲空則會進行喚醒
                LockSupport.park(this);
            }
        }
    }

3.移除waiter:removeWaiter

我們看到註釋上面其實也有提到,這邊是通過cas的方式來實現的無鎖操作,因爲這邊會存在兩個循環,在某些情況下可能從頭開始處理,就是說可能你已經處理到第十個節點了,但是發現有問題就要從頭再來,所以這邊告訴你可能會有效率問題,因爲處理的waiter節點是對應的get操作,人家也說了設計的目的考慮的是waiter是非常少量的,如果你蛇精病死循環去get,你還能怪人家?
這邊方法一進來,先是把當前需要取消的節點的thread設置爲null,我們在上面其實也有看到這樣的操作,其實對於FutureTask來說,當你的waiter的thread爲空的時候就需要被移除了,算是一個標記。
然後我們進入了第一個死循環,而第一個死循環結束的條件是第二個死循環不滿足q != null這個條件纔會結束。
我們來看一下第二個循環,這邊存在三個判斷:
1.先判斷waiter.thread是否爲空,如果不爲空就要往下一個節點去查找了
2.不滿足上述的判斷即 當前節點的p.thread == null並且前一個節點不爲空(這說明不是頭結點),會先把當前節點p斷開,然後再判斷前一個節點pre的thread是不是爲空,這個pre的thread按道理如果是第一次檢查過了不會爲空,但是你永遠想象不到併發的情況下可能它對應的waiter也要取消,把它的thread置空了,那麼這樣就會存在一種極端情況就是執行到這一步的時候,pre這個節點被另一個removeWaiter的線程拋棄了,那麼他的next就是null了,按道理這個時候應該直接break出去,因爲該做的做完了,但是這個removeWaiter真是盡職盡責,他會跳會第一個循環重新再查找一邊,保證所有的該清除的節點都清除掉
3.第三步則是比較特殊的情況就是滿足q.thread == null && pre == null,即剛好要移除的就是第一個waiter,但是移除之後依然會回到一個死循環繼續查找
直到最後,找到最後一個waiter(直到next == null)才退出死循環。

    /**
     * Tries to unlink a timed-out or interrupted wait node to avoid
     * accumulating garbage.  Internal nodes are simply unspliced
     * without CAS since it is harmless if they are traversed anyway
     * by releasers.  To avoid effects of unsplicing from already
     * removed nodes, the list is retraversed in case of an apparent
     * race.  This is slow when there are a lot of nodes, but we don't
     * expect lists to be long enough to outweigh higher-overhead
     * schemes.
     */
    private void removeWaiter(WaitNode node) {
        if (node != null) {
            //清空Thread,把要移除的wait節點做一個標記
            node.thread = null;
            // restart on removeWaiter race
            retry:
            for (; ; ) {
                //這邊的結束條件是 q == null;
                for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                    s = q.next;
                    //如果我們要移除的節點不是頭結點,因爲我們已經設置了Thread = null了.繼續往下一個節點查找
                    if (q.thread != null) {
                        pred = q;
                    }
                    //這個地方顯然是不滿足第一個if纔會進來,即 q.thread == null && pred != null
                    else if (pred != null) {
                        //把q節點斷開了
                        pred.next = s;
                        // check for race
                        //這邊再來一個判斷是因爲在多線程的情況下,可能在上面pred的thread也被另一個線程調用removeWaiter置爲空了。
                        //所以這邊返回頭結點,繼續從頭開始查找處理
                        if (pred.thread == null) {
                            continue retry;
                        }
                    }
                    //不滿足上面條件,即 q.thread == null && pre == null.就是說當前需要移出的元素在棧頂,然後把棧頂元素替換掉.
                    else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s)) {
                        continue retry;
                    }
                }
                break;
            }
        }
    }

3.取消執行:cancel

cancel方法這邊有一個入參是mayInterruptIfRunning,當這個入參爲true的時候表示是中斷取消的,如果是false則是其他原因。
這邊如果不滿足條件不會進行取消,這個條件是:任務的狀態是NEW,如果是NEW,此時會通過CAS設置他的狀態,設置成功才能進入取消,也可能在設置的過程中任務被啓動了,就會失敗,失敗則直接返回
如果設置成功,這邊只會對中斷產生的cancel進行中斷處理,否則直接進入finally中進行處理。finishCompletion方法表示喚醒所有等待的節點,因爲任務已經被取消或者已經處理結束了(就是變成某些終止狀態了,必須進行結束)。

    //入參表示是中斷還是取消節點,true表示中斷,false表示取消
    public boolean cancel(boolean mayInterruptIfRunning) {
        //如果不滿足當前條件的不能取消: 不滿足 新創建的線程並且設置成對應取消狀態,直接返回,也就是說以一旦進入運行是不能取消的,除非還在等待
        if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))){
            return false;
        }
        // in case call to interrupt throws exception
        try {
            //如果是CANCELED只是在上面設置狀態.然後在finally中進行處理
            //如果是中斷會在這邊設置中斷狀態
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null){
                        t.interrupt();
                    }
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            //結束處理,喚醒所有等待的wait節點
            finishCompletion();
        }
        return true;
    }
發佈了84 篇原創文章 · 獲贊 15 · 訪問量 3212
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章