導入ではJavaのプログラミングと創造マルチスレッドのスレッドを、私たちは、Runnableインタフェースを実装し、呼び出し可能インターフェースを実装し、それぞれスレッドを継承し、スレッドを作成する方法を紹介しました。この記事では、我々はスレッドを作成する別の方法、FutureTaskをご紹介します。Runnableの実装と比較して、戻り結果はありません。FutureTaskは、デフォルトの結果を返すことができるようにそれを適応させ、Callableスレッドの作成をサポートします。ここでは、最初に例を使用してFutureTaskの使用法を紹介します。
private static void futureTaskRunnable() throws InterruptedException, ExecutionException {
//构造方法为一个接口Runnable实现和一个默认的返回值
FutureTask<String> future = new FutureTask<String>(()->{
}, "Hello,Word");
//调用run()方法运行线程
future.run();
System.out.println("futureTaskRunnable: " + future.get());
}
private static void futureTaskcallable() throws InterruptedException, ExecutionException {
//构造方法为接口 Callable的实现
FutureTask<String> future = new FutureTask<String>(()->{
return "test";
});
//调用run()方法运行线程
future.run();
System.out.println("futureTaskcallable: " + future.get());
}
上記のコードと同様に、RunnableメソッドとCallableメソッドを使用してFutureTaskを作成し、FutureTaskのrun()メソッドを呼び出してスレッドを開始できます。FutureTaskのクラス階層図によると、FutureTaskはFutureインターフェイスを実装するだけでなく、Runnableインターフェイスも実装します。したがって、FutureTaskをExecutorに渡して実行することができます。
先ほど、FutureTaskがRunnableを適応させてデフォルト値を返すことができると述べました。FutureTaskがRunnableインターフェースにどのように適応するかを見てみましょう。実際、これはアダプターモードです。以下は、FutureTask適応Runnableインターフェースの実装です。
public FutureTask(Runnable runnable, V result) {
//将Runnable接口封装成Callable接口,方法如下
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
//适配器模式,下面我们看RunnableAdapter
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;
}
}
FutureTaskの使用法を紹介した後、FutureTaskの実装原理を紹介します。JDK1.6では、FutureはAbstractQueuedSynchronizerを使用します。JUCパッケージの多くの同時実行関連ツールクラスはAQSクラスを使用します。これは、同時実行ツールの基礎であると言えます。このクラスについて知らない場合は、AQSキューシンクロナイザーAbstractQueuedSynchronizerの簡単な使用法を参照できます。AQSは、同期状態をアトミックに管理し、スレッドをブロックおよびウェイクアップし、ブロックされたスレッドのキューを維持するための一般的なメカニズムを提供する同期フレームワークです。AQSに基づくすべてのシンクロナイザーには、2種類の操作が含まれます。
- 少なくとも1つの取得操作。この操作は、AQSの状態によってこのスレッドが実行を継続できる場合を除いて、呼び出し元のスレッドをブロックします。FutureTaskの取得操作は、get()/ get(long timeout、TimeUnit unit)メソッドと呼ばれます。
- 少なくとも1つのリリース操作。この操作により、AQSの状態が変更され、変更された状態により、1つ以上のブロックされたスレッドのブロックが解除されます。FutureTaskのリリース操作には、run()メソッドとcancel(...)メソッドが含まれます
具体的な実装はここでは紹介しません。AQSの使い方は基本的に似ています。このブログでは、主にFuture in Taskの実装を紹介します。これは、AQSの使用の複雑さに相当します。JDK1.8でのFutureTaskの実装は、よりシンプルで明確です。 get()メソッドから始めます。ソースコードは次のとおりです。
public V get() throws InterruptedException, ExecutionException {
//获取当前FutureTask的状态
int s = state;
//如果未完成,加入到等待队列
if (s <= COMPLETING)
//加入到等待队列,等待线程执行完毕
s = awaitDone(false, 0L);
return report(s);
}
get()メソッドでは、タスクが実行されていない場合、タスクが実行されるのを待つために待機キューに追加されます。ここでは、FutureTaskの待機キューがAQSの待機キューとどのように異なるかを確認します。AQSの待機キューと比較して、FutureTaskより単純に、その定義は次のとおりです。
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
FutureTaskが現在のスレッドを待機キューに追加し、別のスレッドが戻るのを待ってから続行する方法を見てみましょう。その主なロジックはawaitDone()メソッドにあります。Get()とget(long timeout、TimeUnit unit)が最終的に呼び出されます。このメソッドのソースコードは次のとおりです。
//参数time 是否超时,nanos为毫秒数
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
//如果设置了超时,时间相加,否则为0
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) // 任务正在执行,当前线程变为Ready状态,
Thread.yield();
else if (q == null)
q = new WaitNode(); //创建一个WaitINGNode
else if (!queued)
CAS设置值
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
//有超时时间的处理
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
//调用LockSupport
LockSupport.parkNanos(this, nanos);
}
else
//调用LockSupport
LockSupport.park(this);
}
}
上記のコードは、待機キューにスレッドを追加するプロセスです。タスクの実行が完了すると、スレッドは現在のスレッドの実行を継続するために戻ります。以下では、主にrun()メソッドとcancelメソッドで、タスクが実行またはキャンセルされた後のロジックを紹介します。では、最終的に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;
//调用unPart唤醒线程
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
}