JUCソースコード解析 - スレッドプールのパート(B)FutureTask

JUCソースコード解析 - スレッドプールのパート(B)FutureTask

JDK5は、タスクがそれらによって完成された後、後に、タスクの結果を得ることができます呼び出し可能と未来のインターフェイスを提供します。ソースコードの観点から本明細書に具体的な実施原理。

1.はじめにインタフェース

FutureTaskクラス構造

1.1呼び出し可能インターフェース

次のように呼び出し可能インターフェースを実現するために必要なタスクの場合は、呼び出し可能インターフェースの定義は次のとおりです。

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

一般的なインタフェースを見ることができます呼び出し可能、一般的なVは(メソッドの戻り値の型)を呼び出すことです。インターフェースのような呼び出し可能と、Runnableインタフェースは、別のスレッドで実行することができますが、先に述べたように、戻らないのRunnableデータは、例外をスローすることはできません。

1.2今後のインタフェース

将来のインタフェースは、非同期計算の結果を表し、あなたは法によって行わ非同期計算は、今後のインターフェースに提供するかどうかを確認するか、結果を待つと結果を得るだけでなく、実行を取り消すことができます。次のように今後のインタフェースが定義されています。

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;
}
  • )(キャンセル:非同期タスクの実行をキャンセルするために使用。非同期タスクが完了したか、キャンセルされた、または何らかの理由でキャンセルすることができない場合は、falseを返します。タスクが実行されていない場合、それは真の非同期タスクが実行されませんを返します。タスクが既に開始されたが、まだ完全に実装している場合mayInterruptIfRunningがtrueの場合、それはすぐにタスクを実行し、mayInterruptIfRunningがfalseの場合、それはtrueを返し、タスク実行スレッドを中断しませんtrueを返すためにスレッドを中断します。
  • isCanceled() そうでない場合は偽を返し、タスクが終了(正常実行または実行の異常終了の端部)がtrueを返す前にキャンセルした場合、タスクは、キャンセルされたかどうかを決定します。
  • isDone() 完了した場合、タスクが完了しているかどうかを確認、それはそうでない場合はfalseを返し、trueを返します。なお:例外は、タスクの実行中に発生し、タスクが完了したタスクに属してもキャンセルされた、それはtrueを返します。
  • ()GET:タスクがまだ完了していない場合は、タスクの実行が完了するまで待機をブロックします、タスクの実行結果を取得します。タスクがキャンセルされた場合は、例外がタスクの実行中に発生した場合にCancellationException例外をスローされますプロセスが中断された場合ExecutionException例外をスローされますInterruptedExceptionあるが待っているブロックは、例外がスローされます。
  • GET(ロングタイムアウト、TIMEUNIT単位):GETバンドタイムアウト()のバージョンは、プロセスがスーパーリーグを待ってブロックされている場合は、TimeoutException例外がスローされます。

1.3 FutureTask

今後はちょうど、直接オブジェクトを作成するために使用することができないインタフェース、それはFutureTask今後の実装クラスである、構造FutureTaskは次のとおりです。

FutureTask -> FutureRunnable -> Future/Runnable 

FutureTask RunnableFutureインタフェースを実現するために、それを見ることができ、インターフェースはそうではないだけで直接実行RunnableをFutureTaskスレッドとして、またコーラブルの未来としての結果を得るために使用することができ、RunnableFuture、Runnableインタフェースと今後のインターフェイスを拡張します。

2. FutureTaskソースコード解析

2.1 FutureTaskのライフサイクル

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;  // 对执行任务的线程进行中断(未必中断到)

図FutureTask状態の変更:

FutureTask状態

  • NEW:完了していない、実行する新しいタスクまたはタスクを表します。これは初期状態です。
  • 補完-----------------:(例外が発生した場合、例外は保存する理由をタスク実行結果を保存するために使用される結果フィールド)例外がタスクを実行するために発生したときに、タスクが完了または実行されたが、タスクの実行結果や型破りな結果がフィールドに保存されていない時間、状態が完了するNEWから変更されます。しかし、状態は比較的短くなり、この時間は、中間状態です。
  • NORMAL:タスクが実行され、実行結果が結果フィールドに保存されているタスクを完了した、状態はNORMALに完了することから移行します。これは最終状態です。
  • EXCEPTIONAL:タスク実行例外が発生し、異常の原因が結果フィールドに保存された後、状態はEXCEPTIONALに完了することから移行します。これは最終状態です。
  • CANCELED:タスクが開始されていないか、開始されたが、まだ実行時間を完了していない、ユーザーがタスクをキャンセルすると、タスクの実行スレッドを中断しない(偽)メソッドキャンセル呼び出して、状態からのこの時間は、CANCELED NEW状態に変換されるだろう。これは最終状態です。
  • 中断:タスクが開始されていないが、まだ行われていないか、実行が完了すると、ユーザー・コールは、タスクをキャンセルし、タスクの実行スレッドを中断する(true)メソッドをキャンセルするが、スレッドはタスクが中断される前に、状態はNEWから中断に変換されるだろうが実行されていません。これは中間状態です。
  • INTERRUPTED:タスクの実行スレッドの状態が中断に割り込むから変換された後)(割り込みを呼び出して中断。これは最終状態です。

一旦完了し、中間状態を遮断しながら、新しいは、これらの状態を終了するために起動状態、NORMAL例外キャンセル、遮断状態と見なすことができます。なぜ完了し、二つの中間状態を中断紹介しますか?補完----------------- -中央を> INTERRUPTED結果= vが中断し、割り当てプロセスを持っている- > INTERRUPTED t.interruptプロセスを持っています。このプロセスは、他のスレッドの干渉ことを保証することです。(個人的な理解)

もう一つ注意すべきは、ということであるすべての状態が補完-----------------より大きい値は、タスクが実行(キャンセルされた異常実行するには、通常、実行されたタスク、タスクまたはタスク)を完了したことを示しています。

2.2内部構造

// 内部封装的 Callable 对象。如果是 Runnable 则会通过 Executors#callable 封装成 Callable
private Callable<V> callable;
private Object outcome;             // 用于保存计算结果或者异常信息。
private volatile Thread runner;     // 用来运行 Callable 的线程
private volatile WaitNode waiters;  // FutureTask中用了 Trieber Stack 来保存等待的线程
                                    // 这个队列使用 Treiber stack(可以理解为基于 CAS 的无锁的栈,先进后出)

FutureTask Treiberスタック異常なバック/タスク完了FutureTask /取り消し、タスクの完了のためのスレッドが待機を維持するためには、finishCompletionフック方法で待機しているスレッドのスタックを起動します。

コンストラクタ2.3

FutureTaskのコンストラクタで開始することになります。

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

見ることができ、ウェイターの初期値は、初期状態NEW nullです。

一般的には、タスクをキャンセル、スレッドはタスクの実行の結果を得るために、タスクの実行スレッド(通常、スレッドプール)でタスクの実行状態を確認することができます開始、通常、同じスレッドではありませんタスクを実行するスレッドでタスクを実行するための時間を次のタスクスレッドを立ち上げなどの操作は、その後、これらの操作を分析します。

2.4タスクの実行スレッド

2.4.1コアメソッドの実行

executor.submit(タスク)は、スレッドプールのタスクに提出された後、間違いなくFutureTaskは、runメソッドは、設定結果値を完了実行元の梱包方法を、実行しないと、すべての待機中のスレッドを起動します。

/**
 * run 方法执行有两个条件:1. state=NEW; 2. runner=null
 * 1. 执行前 state=NEW & runner=null
 * 2. 执行中 state=NEW & runner=Thread.currentThread()
 * 3. 执行后 state!=NEW & runner=null,根据是否有异常执行 set(result) 或 setException(ex),
 *    set/setException 都会更新 state 状态,之后线程的状态就不是 NEW
 * 因此,多个线程同时调用 run 方法的情况 callable 也只会执行一次
 */
public void run() {
    // 1. state为 NEW 且对 runner 变量 CAS 成功。 对 state 的判断写在前面,是一种优化。 
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            // 2. 是否成功运行的标志位,而不是把 set 方法写在 try 中,是为了不想捕获 set 的异常。
            //    比如:子类覆写 FutureTask#done 方法(set->finishCompletion >done) 抛出异常,
            //    然而实际上提交的任务是有正常的结果的。
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // 3. 如果A/B两个线程同时执行到步骤①,当A线程重新将runner设置为 null 时,B线程是否可能会重新执行呢?
        //    实际上A线程一旦已经调用 set/setException 方法,整个流程已经结束了,所以不可能重复执行
        runner = null;

        // 4. 等待调用 cancel(true) 的线程完成中断,防止中断操作逃逸出 run 或者 runAndReset 方法
        //    以至于线程在执行后续操作时被中断
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

概要: RUN方法は、包装の層であっても呼び出し可能であるが、実行が完了した後に結果を返し、すべての待機中のスレッドを覚まします。

  1. タスクの現在の状態は、タスクNEWないか、または実行された、またはキャンセルされた、直接のリターンを決定します。
  2. 状態が続いCASランナーフィールドに格納されているタスク実行スレッドが参照されます危険なクラスでNEW続いている場合は、そこに一つであり、唯一のスレッドが排他ロックと同等、成功することができます。
  3. タスクの実行が完了すると、通話が設定またはsetException方法、およびすべての待機中のスレッドを覚まします。

2.4.2設定の結果セット/ setException

// 设置返回结果,唤醒等待线程。思考为什么需要 COMPLETING 这个中间状态
protected void set(V v) {
    // 可能任务已经取消,state 的状态就不再是 NEW
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

次のように、正常に実行されたタスクを実行するために、異常またはタスクであるか否か以前の分析によると、またはタスクをキャンセルし、最後finishCompletion()メソッドを呼び出し、メソッドが実装されています。

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        // 1. 必须将栈顶 CAS 为 null,否则重读栈顶并重试。
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            // 2. 遍历并唤醒栈中节点对应的线程。
            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;
        }
    }

    // 3. ExecutorCompletionService#QueueingFuture 中把结果加到阻塞队列里。
    //    CompletionService 谁用谁知道,奥秘全在这。
    done();     // 子类覆盖

    // 4. callable 置为 null 主要为了减少内存开销,更多可以了解 JVM memory footprint 相关资料
    callable = null;        // to reduce footprint
}

このメソッドの実装は、ウェイター、リンクリストを横断、スレッドは、その後、ノードをウェイク呼び出し可能な空、比較的簡単です。スレッドは(それぞれ目覚めする)awaitDone()メソッドLockSupport.parkを遮断から復帰し、その後循環の新しいラウンドを実施します。ループの新ラウンドでは、より正確には、タスクの戻り状態で、結果を返したりします。

2.5タスクの呼び出し元のスレッド

2.5.1結果が得る得ます

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)    // NEW 或 COMPLETING
        s = awaitDone(false, 0L);
    return report(s);
}

public V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    if (s <= COMPLETING &&
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException(); // 超时了,任务还未执行结束
    return report(s);
}

概要:完了したタスクの正常、異常なタスクの実行が含まれ、ここで通話awaitDoneエンドをブロックタスクは現在のスレッドの終わりに実行されていない場合は、タスクがキャンセルされました。タスクの実行が終了すると、コールレポートでは、()の結果を返します。

// report 根据任务最终的状态,返回结果或抛出异常
private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)    // 1. 正常执行完计算任务
        return (V)x;
    if (s >= CANCELLED) // 2. 取消
        throw new CancellationException();
    throw new ExecutionException((Throwable)x); // 3. 异常
}

ミッションの終わりのための2.5.2待機awaitDone

タスクの結果を得るために、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. 调用 get 的线程是否被其他线程中断
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        // 2. 任务已经执行完成,无论是正常、异常、取消
        if (s > COMPLETING) {   // 已经执行完成
            if (q != null)      // help GC
                q.thread = null;
            return s;
        // 3. 结果正在赋值,让出 CPU 等待
        } else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        // 4. 初始化节点,重试一次循环
        else if (q == null)
            q = new WaitNode();
        // 5. queued 记录是否已经入栈,此处准备将节点压栈
        //    节点入队失败,自旋直至成功
        else if (!queued)
            // 这是 Treiber Stack算法入栈的逻辑。 Treiber Stack 是一个基于 CAS 的无锁并发栈实现
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        // 6. 挂起线程
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);    // 超时直接删除节点后返回
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this); // 只有任务取消(cancel)和任务执行结束(run),才能唤醒线程
                                    // 此时再一次for循环时state的状态肯定大于COMPLETING
    }
}

概要: awaitDone以下の状況:

  1. スレッドを取得するための呼び出しが他のスレッド、直接スロー例外:InterruptedExceptionによって中断されているかどうか。
  2. ミッションのリターン結果終わり、ここでの結果は、タスクの状態です。限り、それはタスクの終了を完了より大きい意味します。
  3. チームのスピンスレッドに入るために呼び出し、スレッドがウェイクを待ってハングアップします。唯一のfinishCompletionは、スレッドを覚ますだろうが、この方法だけで作業は中止または終了、実行タスクの実行メソッドが呼び出されますキャンセル。もちろん、タスクのタイムアウト、直接リターン場合は、このステータスは新しいものかもしれません。

2.5.3削除ノードremoveWaiter

/**
 * 清理用于保存等待线程栈里的无效节点,所谓节点无效就是内部的 thread 为 null(类比 ThreadLocalMap)
 *   
 * 一般有以下几种情况:
 * 1. 节点调用 get 超时。
 * 2. 节点调用 get 中断。
 * 3. 节点调用 get 拿到 task 的状态值(> COMPLETING)。
 *
 * 此方法干了两件事情:
 * 1. 置标记参数 node 的 thread 为 null
 * 2. 清理栈中的无效节点
 *
 * 如果在遍历过程中发现有竞争则重新遍历栈。
 */
private void removeWaiter(WaitNode node) {
    if (node != null) {
        node.thread = null;
        retry:
        for (;;) {          // restart on removeWaiter race
            // pre(前继节点) -> current(当前节点) -> next(后继节点),对应下面的 pre -> q -> s
            // 1.1 q.thread!=null 则更新 pre 节点继续遍历
            // 1.2 pre!=null && q.thread==null 时 current 不时头节点,直接删除 current
            // 1.3 pre==null && q.thread==null 时 current 是头节点,更新头节点为 next
            for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                s = q.next;
                // 1.1 保存当前节点 q 的前继节点 pre
                if (q.thread != null)
                    pred = q;
                // 1.2 q 节点已经失效,此时根据 q 是否是头节点分两种情况
                //     q 不是头节点,直接删除,为什么不需要原子性操作?
                else if (pred != null) {
                    pred.next = s;              // 踢除当前节点 q
                    if (pred.thread == null)    // check for race
                        continue retry;
                // 1.3 q 是头节点且失效了,原子性更新头节点
                } else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
                    continue retry;
            }
            break;
        }
    }
}

2.5.4は、キャンセルタスクをキャンセル

/**
 * mayInterruptIfRunning:false 时不允许在线程运行时中断,true 时允许运行时中断线程,但不保证一定会中断线程。
 * 1. true 时,将状态修改成 INTERRUPTING,执行 thread.interrupt()
 * 2. false 时,将状态修改成 CANCELLED
 */
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();  // interrupt 不一定能中断线程
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion(); // 唤醒所有等待线程
    }
    return true;
}

概要:キャンセル方法は、以下のことを行います。

  1. タスクのステータスを変更します。mayInterruptIfRunningと呼ばれるパラメータ名は、それ以来、タスクが割り込みによって停止することができないかもしれないことに注意してください。ビジネスは、応答スレッドが中断するかどうかに依存します。条件がtrueに変更されましたが、このような次のコードのように、タスクを中断することはできません単にインクルードキャンセル呼び出します。

    // !Thread.interrupted() 改为 true 后无法中断任务
    Future<?> future = executorService.submit(() -> { 
        while (!Thread.interrupted()) System.out.println("1"); 
    });
    future.cancel(true);
  2. モーニングコールfinishCompletionは、すべてのタスク・スレッドの結果を待っています。

参考:

  1. :この記事は、「綿密な調査FutureTask、」に再現されhttp://www.importnew.com/25286.html
  2. "FutureTaskソースの解釈":https://www.cnblogs.com/micrari/p/7374513.html

毎日少しを記録する意向。おそらく、内容は重要ではありませんが、習慣は非常に重要です!

おすすめ

転載: www.cnblogs.com/binarylei/p/10958885.html