ExecutorService
を使用するとsubmit
がCallable
タスクを返し、Future
を返すことができるので、なぜFutureTask
を使用してCallable
タスクをラップし、メソッドexecute
を使用する必要があるのですか?どちらも同じことをしていると思う。
実際、あなたは正しいです。 2つのアプローチは同じです。通常、自分でラップする必要はありません。もしそうなら、AbstractExecutorServiceでコードを複製している可能性があります。
/**
* Returns a <tt>RunnableFuture</tt> for the given callable task.
*
* @param callable the callable task being wrapped
* @return a <tt>RunnableFuture</tt> which when run will call the
* underlying callable and which, as a <tt>Future</tt>, will yield
* the callable's result as its result and provide for
* cancellation of the underlying task.
* @since 1.6
*/
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
FutureとRunnableFutureの唯一の違いは、run()メソッドです。
/**
* A {@link Future} that is {@link Runnable}. Successful execution of
* the <tt>run</tt> method causes completion of the <tt>Future</tt>
* and allows access to its results.
* @see FutureTask
* @see Executor
* @since 1.6
* @author Doug Lea
* @param <V> The result type returned by this Future's <tt>get</tt> method
*/
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
ExecutorにFutureTaskを作成させる正当な理由は、FutureTaskインスタンスに複数の参照が存在する可能性のある方法がないことを保証するためです。つまり、エグゼキューターownsこのインスタンス。
FutureTask このクラスはbase implementation of Future
、計算を開始およびキャンセルするメソッド
将来 はインターフェースです
Future
は単なるインターフェイスです。舞台裏では、実装はFutureTask
です。
絶対に手動でFutureTask
を使用できますが、Executor
(プールスレッド、スレッドの制限など)を使用する利点は失われます。 FutureTask
の使用は、古いThread
の使用とrunメソッドの使用に非常に似ています。
FutureTaskを使用する必要があるのは、その動作を変更するか、後でCallableにアクセスする場合のみです。 99%の用途では、CallableとFutureを使用してください。
Markや他の人たちは、Future
がFutureTask
とExecutor
のインターフェースであり、事実上そのファクトリーであると正しく答えました。つまり、アプリケーションコードがFutureTask
を直接インスタンス化することはほとんどありません。議論を補完するために、FutureTask
が構築され、Executor
の外部で直接使用される状況を示す例を提供しています。
_ FutureTask<Integer> task = new FutureTask<Integer>(()-> {
System.out.println("Pretend that something complicated is computed");
Thread.sleep(1000);
return 42;
});
Thread t1 = new Thread(()->{
try {
int r = task.get();
System.out.println("Result is " + r);
} catch (InterruptedException | ExecutionException e) {}
});
Thread t2 = new Thread(()->{
try {
int r = task.get();
System.out.println("Result is " + r);
} catch (InterruptedException | ExecutionException e) {}
});
Thread t3 = new Thread(()->{
try {
int r = task.get();
System.out.println("Result is " + r);
} catch (InterruptedException | ExecutionException e) {}
});
System.out.println("Several threads are going to wait until computations is ready");
t1.start();
t2.start();
t3.start();
task.run(); // let the main thread to compute the value
_
ここでは、FutureTask
または同様のバリアプリミティブのように、CountdownLatch
が同期ツールとして使用されます。 CountdownLatch
またはロックと条件を使用して再実装できます。 FutureTask
は、カプセル化された、わかりやすく、エレガントで、より少ないコードで作成できます。
また、どのスレッドでもFutureTask#run()メソッドを明示的に呼び出す必要があることに注意してください。あなたのためにそれをするための実行者がいません。私のコードでは、最終的にメインスレッドによって実行されますが、get()
メソッドを変更して、run()
を呼び出す最初のスレッドでget()
を呼び出すことができます。スレッドがget()
に到達し、T1、T2、またはT3のいずれかになり、残りのすべてのスレッドの計算を行います。
このアイデア-最初のスレッドが結果を要求すると他のスレッドの計算が行われ、同時試行はブロックされます-はMemoizerに基づいています。「Java同時実行性の実践」の108ページのMemoizer Cacheの例を参照してください。