Callable
インターフェイスと同様にFuture
、Java の以降のバージョンで導入され、インターフェイスを実装するクラスとインターフェイスを実装するクラスの両方をスレッドによって実行されるタスクに使用できます。Callable
Runnable
Callback
Runnable
以下は、2 つのインターフェイスに関連するソース コードです。
// /xref/libcore/ojluni/src/main/java/java/util/concurrent/Callable.java
public interface Callable<V> {
V call() throws Exception;
}
// /xref/libcore/ojluni/src/main/java/java/lang/Runnable.java
public interface Runnable {
public abstract void run();
}
上記のソース コードからわかるように、Callable.call()
メソッドには戻り値があり、例外をスローできますが、Runable.run()
メソッドには戻り値がなく、例外はスローされません。
Callable
インターフェイスはRunnable
インターフェイスの補足として見ることができ、インターフェイスRunnable
との違いは、タスクの実行結果を返し、例外をスローできる点であり、通常はThreadPoolExecutor
組み合わせて使用されます。
Future
タスクをキャンセルしRunnable
たり、タスクがキャンセルされたかどうかを判断したり、タスクが完了したかどうかを問い合わせたり、タスクの結果を取得したりできるインターフェイスです。関連するソースコードはCallable
次のとおりです。Future
public interface Future<V> {
// 取消任务的执行。任务已经完成或者已经被取消时调用此方法,会返回 false
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消
boolean isCancelled();
// 判断任务是否完成。正常完成、异常以及被取消,都将返回 true
boolean isDone();
// 阻塞地获取任务的执行结果
V get() throws InterruptedException, ExecutionException;
// 在一定时间内,阻塞地获取任务的执行结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
ただし、Future
は単なるインターフェイスであるため、その中の関数を使用するには、FutureTask
非同期計算の結果を表す間接実装クラス を使用する必要があります。
FutureTask
はRunnableFuture
インターフェイスの実装クラスであり、RunnableFuture
インターフェイスはRunnable
およびFuture
インターフェイスを実装します。したがって、FutureTask
引き渡しThread
またはExecutor
実行することができます。
FutureTask
の継承関係は次のとおりです。
FutureTask.run()
メソッドが実行されるタイミングにより、FutureTask
以下の 3 つの状態になります。
- 未開始:
FutureTask.run()
メソッドが実行される前は、未開始状態です。 - 開始済み:メソッドは
FutureTask.run()
実行中FutureTask
開始済み状態です。 - Completed:
FutureTask.run()
メソッドの実行が完了した後、正常に終了した後、または実行FutureTask.cacel(boolean)
、キャンセル、または実行された後FutureTask.run() 方法时抛出异常而结束
、完了状態になります。
以下は のFutureTask
状態遷移図です。
FutureTask.get()/FutureTask.get(long, TimeUnit)
方法について:
FutureTask
が未開始または開始済みの状態にある場合、FutureTask.get()/FutureTask.get(long, TimeUnit)
メソッドを実行するとスレッドがブロックされます。FutureTask
が完了状態にあるときにFutureTask.get()/FutureTask.get(long, TimeUnit)
メソッドを実行すると、呼び出し元のスレッドは結果をすぐに返すか、例外をスローします。
FutureTask.cancel(boolean)
方法について:
- が開始されていない場合
FutureTask
、FutureTask.cancel(boolean)
メソッドを実行すると、このタスクは実行されません。 FutureTask
が開始状態にある場合、executeFutureTask.cancel(true)
メソッドはタスクを実行しているスレッドを中断してタスクを停止しようとします。FutureTask
開始状態の場合、実行FutureTask.cancel(false)
メソッドは実行中のタスクのスレッドに影響を与えません (実行中のタスクを完了まで実行させます)。- が完了した状態になると
FutureTask
、executeFutureTask.cancel(boolean)
メソッドは を返しますfalse
。
実行のためにFutureTask
に引き渡すことも、メソッドを通じてオブジェクトを返すこともできますが、 はインターフェイスであるため、通常はオブジェクトがここで返されます。その後、メソッドまたはメソッドを呼び出すことができます。Executor
<T> Future<T> ExecutorService.submit(Runnable, T)/Future<?> ExecutorService.submit(Runnable)
Future
Future
FutureTask
FutureTask.get()/get(long, TimeUnit)
FutureTask.cancel(boolean)
これは、スレッドが続行する前に、別のスレッドがタスクを完了するのを待つ必要がある場合に使用できますFutureTask
。複数のタスクを実行する複数のスレッドがあるとします。複数のスレッドが同じタスクを同時に実行しようとすると、1 つのスレッドだけがタスクの実行を許可され、他のスレッドは実行を続ける前にタスクが完了するまで待機する必要があります。
FutureTask
のコードは次のとおりです。
private final ConcurrentHashMap<Object, Future<String>> taskCache = new ConcurrentHashMap<>();
private String executionTask(final String taskName) {
while (true) {
Future<String> future = taskCache.get(taskName); // 1.1 2.1
if (future == null) {
Callable<String> task = new Callable<String>() {
@Override
public String call() throws Exception {
return taskName;
}
};
FutureTask<String> futureTask = new FutureTask<>(task);
future = taskCache.putIfAbsent(taskName, futureTask); // 1.3
if (future == null) {
future = futureTask;
futureTask.run(); // 1.4 执行任务
}
}
try {
return future.get(); // 1.5 2.2 线程在此等待任务执行完成
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
以下はコードの概略図です。
2 つのスレッドが同時に同じタスクを実行しようとした場合、スレッド 1 が 1.3 を実行し、スレッド 2 が 2.1 を実行すると、スレッド 2 は 2.2 で待機し、スレッド 1 が 1.4 を実行するまで待機してから、スレッド 2 は 2.2 から戻ります。
FutureTask
使用方法は次のとおりです。
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
Thread.sleep(100);
futureTask.cancel(true); // 1
System.out.println("future is cancel: " + futureTask.isCancelled());
if (!futureTask.isCancelled()) {
System.out.println("future is cancelled");
}
System.out.println("future is done: " + futureTask.isDone());
if (!futureTask.isDone()) {
System.out.println("future get = " + futureTask.get()); // 2
} else {
System.out.println("task is done");
}
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "-call is running");
Thread.sleep(5000);
return "Hello World";
}
}
実行結果は以下の通りです。
注 1 でタスクの実行をキャンセルしてみます。タスクが完了またはキャンセルされたときにメソッドが return する場合、false
完了していないタスクをキャンセルできる場合は return しますtrue
。なぜなら、上記のコードではタスクがまだ休止状態にあるため、タスクをキャンセルできます。注 2 では、FutureTask.get()
タスクの実行が完了して結果が返されるまで、メソッドは現在のスレッドをブロックします。
Callable
およびとともにFuture
使用する場合:
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Callable<String> call = new MyCallable();
Future<String> future = executorService.submit(call);
executorService.shutdown();
Thread.sleep(5000);
System.out.println("主线程休眠 5 秒,当前时间: " + System.currentTimeMillis());
String str = future.get();
System.out.println("Future 已拿到数据,str = " + str + "; 当前时间为: " + System.currentTimeMillis());
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("进入 call 方法,开始休眠,休眠的时间为: " + System.currentTimeMillis());
Thread.sleep(10000);
return "Hello World";
}
}
結果:
上記のコードでは、future
実行のためにスレッド プールに直接入れられます。実行結果から判断すると、メインスレッドは5秒スリープしますが、call()
タスクの結果はメソッド実行から得られるため、両者の時間差は10秒あります。FutureTask.get() 方法会阻塞当前线程直到任务完成。
ExecutorService.shutdow()
SHUTDOWN
この時点では、スレッド プールは新しいタスクを受け取ることができず、すべてのタスクが実行されるまで待機します。このメソッドが呼び出された場合ExecutorService.shutdownNow()
、スレッドはSTOP
その状態にあり、スレッド プールは現時点では新しいタスクを受け入れることができず、実行中の結果を終了しようとします。
FutureTask
同じ効果は次の方法でも実現できます。
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Callable<String> call = new MyCallable();
FutureTask<String> task = new FutureTask<>(call);
executorService.submit(task);
executorService.shutdown();
Thread.sleep(5000);
System.out.println("主线程休眠 5 秒,当前时间: " + System.currentTimeMillis());
String str = task.get();
System.out.println("Future 已拿到数据,str = " + str + "; 当前时间为: " + System.currentTimeMillis());
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("进入 call 方法,开始休眠,休眠的时间为: " + System.currentTimeMillis());
Thread.sleep(10000);
return "Hello World";
}
}
結果:
上記の組み合わせにより次のような変化が生じます。シナリオがある場合、メソッド A がデータを返すのに 10 秒かかり、メソッド A の背後にあるコードの実行には 20 秒かかりますが、20 秒の実行結果では、後半の 10 秒だけがかかります。メソッド A の実行結果に依存します。従来と同じ方法だとどうしても数十秒の時間が無駄になってしまいますが、最初の2つの組み合わせを使えば効率が上がります。
- まず、メソッド A の内容を
Callable
実装クラスのメソッドに入れますcall()
。 - メインスレッドのスレッドプールを通じてタスク A を実行します。
- メソッドAの実行結果に依存しない以下のメソッドのコードを10秒間実行します。
- メソッド A の実行結果を取得し、次のメソッドでメソッド A の実行結果に依存するコードを 10 秒間実行します。
これにより、コードの効率が向上し、プログラムが A メソッドに固執する必要がなくなります。