Javaソース分析-FutureTask

8518 ワード

前にAndroidの中のAsyncTaskを分析した時にFutureTaskという種類に言及しました。その時は思考が制限されていて、この類に対する認識がまだ足りないかもしれないので、説明の面ではよくないかもしれません。今日はSchduledThreadPoolExector類のソースコードを見ていますので、FutureTask類に関連しています。まずFutureTaskの意味を理解しなければならないと思います。ScheduledThreadPoolExectorに対する理解はもっとはっきりしているかもしれません。  本文の参考資料:
  1.FutureTask深さ解析
  2.方騰飛、魏鵬、程暁明の『Java同時プログラミングの芸術』
1.FutureTaskの紹介
このクラスを正式に分析する前に、まずFutureTask類に対して全体的な認識があります。まず、Future TaskはFutureインターフェースとRunnableを実現したので、FutureTask類はThreadにタスクとして実行されます。Futureインターフェースを実現するのは主にマークとして使われていましたが、このタスクは実行時にキャンセルされ、非同期実行の結果も得られます。これはRunnableインターフェースではできません。また、FutureTaskはRunnableインターフェースを実現したので、Exectorによって実行されることができます。これもなぜScheduledThreadPoolExectorにFuture Taskクラスが使われていますか?ここまで言ったら、この類に対してちょっと疑問があるかもしれません。焦らないでください。今は原理的にこの種類が何なのか分かります。
2.FutureTaskのメンバー変数
FutureTaskクラスを分析する前に、私たちはやはりこのFutureTaskクラスのメンバー変数に対して基本的な理解があります。このようにして、後のソースコードを理解する時、私たちに一定の助けがあります。
変数名
タイプ
説明
state
要点
現在のタスクが実行されている状態は、複数の状態から選択できます。あとで詳細に各状態の意味を紹介します。
ケーブル
Callable
実行するジョブクラスは、Callableのcallメソッドを呼び出して、Callable内部のタスクを実行することができます。Callableのコールを本当に呼び出す方法は、FutureTaskのrunメソッドの中にあります。
outcome
Object
タスク実行中に発生した結果、このオブジェクトに格納されているのは正常実行後に発生する可能性があり、実行中に発生する異常である可能性があります。
ルnner
Thread
現在タスクを実行しているスレッドは、一つのFutureTaskタスクが複数のスレッドによって実行される可能性があるので、ここではルナーが実行中のスレッドを記録します。
waiters
WaitNode
スタックの要素を待ちます。スレッドがタスクの結果を取得しようと試みると(getメソッドを呼び出す)、まずWaitNodeのオブジェクトを作成してスタックに入る。うまく結果が得られたら、このスレッドをスタックから削除します。結果が得られないと(まだタスクが実行されていません)、ブロックされます。タスクが完了したら、finish Complectionメソッドで呼び覚まされます。
上記の解釈stateによると、stateは複数の状態があるかもしれません。それぞれの状態の意味を見に来ました。
変数名

説明
NEW
0
新しい状態で、まだ実行が開始されていないことを示します。
COMPLETING
1
state中間状態の一つは、任務が完了するという意味ですが、最終的な処理がまだできています。ここに疑問があるかもしれませんが、後はソースコードから分かります。
NORMAL
2
stateの最終状態の一つは、任務が正常に完了したことを表しています。タスクの状態がNORMALであれば、outcomeに格納されているのはタスク実行の結果です。
EXCEPT IONAL
3
stateの最終状態の一つは、タスクが実行中に異常が発生したことを表しています。この場合、outcomeに格納されているのは、発生した異常な対象です。
CANCELL LED
4
state中間状態の一つは、このタスクがキャンセルされていることを表しています。cancelメソッドを呼び出した場合、mayInterruptIfRunningをfalseに設定すると、stateの中間状態はCANCELL LEDに更新されます。
INTERUPTING
5
state中間状態の一つは、このタスクがキャンセルされていることを表しています。cancelメソッドを呼び出した場合、mayInterruptIfRunningをtrueに設定すると、stateの中間状態はCANCELL LEDに更新されます。
INTERUPTED
6
stateの最終状態の一つは、ジョブがキャンセルで呼び出されたことを表しています。これはキャンセルというが、実はスレッドの中断ビットが中断と表記されています。
私たちは今FutureTaskのメンバー変数について初歩的な理解を持っています。後の分析過程で比較的簡単になるはずです。
3.構造方法
メンバー変数に対して基本的な理解ができました。FutureTaskの構造方法を見に来ました。その構造方法の中でどのようなものが初期化されているかを確認してください。FutureTaskには二つの構造方法があります。その中の一つだけを見ましょう。
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
  構造過程は非常に簡単で、二歩しかしませんでした。1.callble変数を初期化します。ここで注意したいのは、Runnableオブジェクトを一つのCallableオブジェクトに包装しました。2.stateを初期化し、NEWに初期化します。
4.runプロセス
作成したFutureTaskオブジェクトをスレッド池に提出したり、Threadを使って実行したりすると、最終的にはrunメソッドを呼び出すので、run方法を分析する必要があります。まずrun方法のコードを見てみます。
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable 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);
        }
    }
ルン方法全体のプロセスは比較的簡単で、2ステップに分けます。1.callbleのcall方法を呼び出して、任務を実行します。2.実行が完了したら、結果を設定します。この結果は異常かもしれません。正常な結果かもしれません。  がタスクの実行中に異常が発生したら、set Exception方法を呼び出します。
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
最初にstateフィールドをCOMPLETINGに更新してみて、それから発生した異常な対象はoutcome変数に割り当てられます。最後にstateをEXCEPTIONALに更新します。プロセス全体が正常に終了すると、セット方法を呼び出して結果を設定します。
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
  set方法の実行過程はset Exception方法と同じです。Outcomeに保存されているのはコール方法から得られた結果です。そしてstateの最終値はNORMALです。
5.getプロセス
  FutureTaskは、非同期の過程から実行結果を得ることができるという特徴があります。この取得方法はgetメソッドを呼び出すことです。しかし、この方法を呼び出すために必要な注意は、現在のタスクがまだ実行されていない場合、成功するまではgetメソッドにブロックされます。getの方法を見に来ました。
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
現在のタスクが完了していないということは、もしstateがCOMPLETING以上に更新されていないなら、awaitdone方法を呼び出してタスクの完了を待つということです。タスクが完了すると、レポートメソッドを直接呼び出して結果を返します。ここでまずレポートの方法を見てみます。
    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);
    }
_; レポート方法は主にタスクに戻る結果です。もちろん、前のタスク実行中に異常が発生したら、ここで異常が発生します。  ここで、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 (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            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);
        }
    }
この方法では、実行プロセスは2ステップに分けられます。1.この方法に入ると、まずWaitNodeを作成して、彼を待機させます。2.自己閉塞を行う。このスレッドが呼び覚まされたり、初めてこの方法を実行した後、自分が中断されていることを発見したら、まず自分のWaitNodeを待合室から取り除いて、次にInterrupteExceptionを投げます。この方法は、状態がCOMPLETINGより大きい場合のみ終了します。
6.キャンセルプロセス
   はFutureTaskを使う時、cancelの操作もよくあります。ここで、cancelについて分析してみます。
    public boolean cancel(boolean mayInterruptIfRunning) {
        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;
    }
  cancelの意味は非常に簡単で、主な役割はスレッドの中断位置を中断と表記し、他の過程を参考にすることです。