blog

Javaマルチスレッド-FutureTaskはどのように戻り値を取得するには?

これは、Javaのマルチスレッドの始まりであり、この記事では、主にスレッドの状態についての簡単なチャット、作成するためのいくつかの方法であり、次に分析するためのビューのソースコードのポイントから、この...

Feb 16, 2020 · 9 min. read
シェア

簡単

これは、Javaのマルチスレッドの開口部は、この記事は、主にスレッドの状態についての簡単なチャット、作成するためのいくつかの方法であり、その後、ソースコードの観点からFutureTaskを分析するために、FutureTaskクラスCallableの両方と関連し、Runnableに関連付けられており、非常によく管理することができますFutureインターフェイスの実装です。CallableまたはRunnable。

スレッド:スレッドは、オペレーティング・システムがスケジューリングできる最小単位で、プロセスの実行に含まれます。

スレッドのいくつかの状態

 public enum State {
 NEW,
 RUNNABLE,
 BLOCKED,
 WAITING,
 TIMED_WAITING,
 TERMINATED;
 }
  1. 初期状態 New: 新しいスレッドが作成され、まだ開始されていない、スレッド作成の初期状態。
  2. 実行状態Runnable:スレッド呼び出しStartメソッドは、この時点でスレッドの状態を準備状態に、この時間は、CPUが実行可能なタイムスライスの割り当てをスケジュールするために待機する必要がある場合は、実行状態に割り当て、Runnableは準備ができているとrunnding一般的な用語〜が実行中と呼ばれています。
  3. ブロックされた:この状態は、Synchronizedメソッドまたはコードブロックに入ることです、この時点でスレッドは、モニタロックオブジェクトを取得するために待機する必要があります、ちなみに、モニタオブジェクトは、内部のJVM内の各オブジェクトは、対応するモニタとその関連付けを持っている、すべての知っているSynchronizedです。同期はmonitorEnterとmonitorexitを介して達成され、JVMがmonitorEnterに実行すると、それは現在のオブジェクトに関連付けられているモニタオブジェクトを取得するために行くだろうし、現在のモニタがロックされた状態になり、時間のこの側の実行の背後にあるスレッドは、モニタオブジェクトへのアクセスを待機する必要があり、スレッドの状態は、ブロッキング状態になります。スレッドの状態は、ブロッキング状態になります。
  4. 待機:スレッドが Object#wait、LockSupport#park、または Thread#join に遭遇すると、スレッドは待機状態に入り、ウェイクアップシグナルを待ちます!
  5. Time-Waiting:これは限られた待ち時間で、上に挙げたメソッドは、メソッドを待っている待ち時間があり、このメソッドの実行はスレッドの状態Time-Waitingです。
  6. Terminated 実行完了後のスレッドの状態。

数種類のスレッド作成

この質問に関して、ある人は3つの方法があると言い、ある人は2つの方法があると言います。私はオラクルの公式サイトを読みましたが、実際には2つの作成方法があると書いてありました:docs.oracle.com/en/java/jav...

スレッド・インターフェースを直接継承

これは最初の方法で、Threadインターフェイスを直接継承します。

class MyThread extends Thread {
 public MyThread() {
 this.setName("MyThread");
 }
 @Override
 public void run() {
 System.out.printf("%s is runing", Thread.currentThread().getName());
 super.run();
 }
 public static void main(String[] args) {
 MyThread myThread=new MyThread();
 myThread.start();
 }
}

実行可能

 <!--Runnable.java-->
 public interface Runnable {
 public abstract void run();
 }
 public static void main(String[] args) {
 Runnable runnable=new Runnable() {
 @Override
 public void run() {
 System.out.printf("%s is runing", Thread.currentThread().getName());
 }
 };
 Thread runnableThread=new Thread(runnable);
 runnableThread.start();
 }

呼び出し可能

実際、このやり方は上のやり方と本質的に同じです。

CallableはFutureインターフェイスで使用するもので、通常の使い方は次のようになります。

 <!--Callable.java-->
 public interface Callable<V> {
 V call() throws Exception;
 }
 Callable<String> callable = new Callable<String>() {
 @Override
 public String call() throws Exception {
 return Thread.currentThread().getName() + "---burgxun";
 }
 };
 FutureTask<String> futureTask = new FutureTask(callable);
 Thread callableThread = new Thread(futureTask);
 callableThread.start();
 String msg = futureTask.get();
 System.out.println(msg);

フューチャータスク

FutureTask このクラスの継承構造を見てください。

public class FutureTask<V> implements RunnableFuture<V> {}
public interface RunnableFuture<V> extends Runnable, Future<V>{}

FutureTask は基本的に Runnable インターフェースと Future インターフェースを実装しているので、new Thread() が作成されたときに FutureTask オブジェクトを渡すことができます。また、Future インターフェースは Callable の実行結果を取得するために使用されるので、FutureTask#get メソッドを使用して Callable の実行結果を取得できます。Callable の実行結果

FutureTaskオブジェクトはタスク管理クラスで、2つのタスクがあり、1つはCallableの戻り値で、Runableの戻り値はありません。

 /** これがFutureTaskの役割だ。Callable */
 private Callable<V> callable;
 /** getで取得した値は出力側から取得する。*/
 private Object outcome; // non-volatile, protected by state reads/writes
 /** FutureTaskを実行するスレッド */
 private volatile Thread runner;
 
 public FutureTask(Callable<V> callable) {
 if (callable == null)
 throw new NullPointerException();
 this.callable = callable;
 this.state = NEW; // ensure visibility of callable
 }
 
 public FutureTask(Runnable runnable, V result) {
 this.callable = Executors.callable(runnable, result);
 this.state = NEW; // ensure visibility of callable
 }

まず、FutureTaskにはcallableとrunnableの2つのコンストラクタがあり、callable変数にあるFutureTaskが実行するタスクを格納するのに使われるのですが、こちら側で注意するところがあります Executors.callable(runnable , result) こちら側 これはタスク実行ツールクラスであるExecutorsクラスの使い方で、具体的には次のコードを見てください。これは、タスク実行ツールクラスであるExecutorsクラスの使い方です:

 <!--Executors.java-->
 public static <T> Callable<T> callable(Runnable task, T result) {
 if (task == null)
 throw new NullPointerException();
 return new RunnableAdapter<T>(task, result);
 }
 <!--RunnableAdapterエクゼキュータの内部クラスである。-->
 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;
 }
 }

見下ろす 実際には、RunnableAdapterの実装は非常に単純ですが、RunnableAdapterは、アダプタは、RunnableをCallableに変換するために使用される、コンストラクタを参照結果に、通常の使用はされません、良いにnullを渡します。

未来

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

Futureインターフェイスで定義されているメソッドを見て、あなたはGetメソッドを見ることができ、ブロックされた時間、および2つのメソッドのタスクの状態を決定するために使用されるgetメソッドがあり、タスクをキャンセルするメソッドは、FutureTaskはRunnableFutureを継承し、RunnableFutureはFutureを実装し、そのFutureTaskも間接的にFutureインターフェイスを実装しています。RunnableFutureはFutureを実装し、FutureTaskも間接的にFutureインターフェイスを実装しています。

取得方法

Callable の実行結果を取得する get メソッドが 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;//タスクの中断
 /**
 * @throws CancellationException {@inheritDoc}
 */
 public V get() throws InterruptedException, ExecutionException {
 int s = state;//state それはタスクの状態だ
 if (s <= COMPLETING)// COMPLETING以下であれば、ステート値は以下のようになるだけだ。 new,COMPLETING
 s = awaitDone(false, 0L);//待ち時間に参加するのは無限待ち
 return report(s);
 }
 
 private V report(int s) throws ExecutionException {
 Object x = outcome;
 if (s == NORMAL)//NORMAL ステータスはタスクの実行が完了したことを示し、値は出力から取得される。
 return (V)x;
 if (s >= CANCELLED)
 throw new CancellationException();
 throw new ExecutionException((Throwable)x);
 }
 <!--値を取得したスレッドは待機リストに追加される。-->
 private int awaitDone(boolean timed, long nanos)
 throws InterruptedException {
 final long deadline = timed ? System.nanoTime() + nanos : 0L;//期限を待つ、timedはfalseの値である0
 WaitNode q = null;//Waitingノードは内部クラスである
 boolean queued = false;
 for (;;) {//スピン put waitNode ノード
 <!--待機中は、スレッドの割り込みステータスを検出して、割り込みステータスに対応するために使われる。-->
 if (Thread.interrupted()) {
 removeWaiter(q);
 throw new InterruptedException();
 }
 int s = state;//タスクの最初の状態が
 if (s > COMPLETING) {// タスクの状態がすでに1より大きければ、次の状態になり、タスクが完了したとみなされてループを抜ける。
 if (q != null)
 q.thread = null;
 return s;
 }
 else if (s == COMPLETING) // タスクがまだ実行中であれば、yieldを呼び出して、現在割り当てられているCPUタイムスライスを吐き出し、再度勝負する。
 Thread.yield();
 else if (q == null)//この時点で、そのsの状態は0、つまり新しい状態、待機ノードの作成のこちら側になるはずだWaitNode
 q = new WaitNode();
 else if (!queued)
 <!--CASを使って、現在のwaitNodeノードをwaitersのバックノードに入れると、初回が実行される。-->
 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);//待機期限を設定しないと、スレッドはブロックされ、スレッドの状態は待機状態になり、実行後にタスクが起きるのを待つことになる。
 }
 }

上記のフローを読んだ後、取得メソッドで値を取得する方法を知って、実際には、取得する結果フィールドのFutureTaskから。取得メソッドは、取得と呼ばれるが、タスクが完了していないときに時間の戻り値を取得することはできません、それはスレッドがブロックされます!

ランメソッド

Getメソッドを通して、結果が出力からの値であることを知っています。

 public void run() {
 <!--現在のタスクの状態を判断する 新規でない場合は、タスクが直接開始されている return; CASは、現在のタスクの実行スレッドが失敗した場合、また、返す設定~-->
 if (state != NEW ||
 !UNSAFE.compareAndSwapObject(this, runnerOffset,
 null, Thread.currentThread()))
 return;
 try {
 Callable<V> c = callable;
 if (c != null && state == NEW) {//タスクの状態は初期状態でなければならない。New
 V result;
 boolean ran;//タスクが完了したかどうか
 try {
 result = c.call();//タスクを実行する主なロジック
 ran = true;//完了
 } catch (Throwable ex) {
 result = null;
 ran = false;
 setException(ex);
 }
 if (ran)
 set(result);//タスクの実行が完了したら、Result値に結果をセットする。
 }
 }
 }
 
 protected void set(V v) {
 //現在のFutureTaskの実行中のタスクの実行状態を変更する この側は非常に奇妙で、NORMALの代わりに実行中の状態が変更されている 実行が完了した状態は、まだ値が割り当てられていないので、タスクの実行が完了したとカウントされない アウトプットのこの側にあるべきである
 if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
 outcome = v;
 UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 上記を言い終わったところで、こちら側が来た。これでタスクの状態が変わり、NORMALがタスクの最終的な状態になる。
 finishCompletion();//タスクが完了した後、実行すべきアクションは、値を取得するためにブロッキング・スレッドをウェイクアップすることである。
 }
 }
 
 /**
 * Removes and signals all waiting threads, invokes done(), and
 * nulls out callable.
 * 待機しているスレッドをすべて削除して起こし、done()メソッドを実行し、FutureTaskのcallableをnull
 */
 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 nullを設定してチェーンテーブルからポイントを切り離すと、GCがポイントを回収するのに役立つ。 到達可能性解析アルゴリズムであるべきで、ポイントを切り離した後、ポイントは到達不可能であり、回収すべきオブジェクトである。
 q = next;// 
 }
 break;
 }
 }
 done();// これは空のメソッドで、継承してサブクラスで実装することができる。
 callable = null; // 現在のタスクが直接実行され、タスク本体がnull
 }

上記のRunメソッドのコメントから、どのようにoutComeの値を代入し、どのように待機中のブロッキングスレッドをウェイクアップし、最後にgetで値を取得するかは明らかでしょう。

概要

この記事は、FutureTaskが何をしているのか、どのようにRunnable、Callable associated、Callableに行くのか、どのように実行結果から結果を得るのかを理解するのに役立つと思います!

Read next