Executor インターフェイスが必要な理由は何ですか?
Java 5 より前は、アプリケーション内で独自のコード スレッド管理をすべて記述する必要がありました。さらに、新しいスレッドobject はリソースを大量に消費する操作であり、軽量タスクごとに新しいスレッドを作成するのは意味がありません。そして、この問題はマルチスレッド アプリケーションのすべての開発者にとってよく知られた問題であるため、この機能をExecutorフレームワークとして Java に導入することにしました。
大きなアイデアとは何ですか? それは簡単です。新しいタスクごとに新しいスレッドを作成する代わりに、スレッドは一種の「ストレージ」に保持され、新しいタスクが到着すると、新しいスレッドを作成する代わりに既存のスレッドを取得します。
このフレームワークの主なインターフェイスはExecutor、ExecutorService、およびScheduledExecutorServiceであり、それぞれが以前のインターフェイスの機能を拡張しています。
Executor インターフェイスは基本インターフェイスです。Runnableオブジェクトによって実装される単一のvoidexecute(Runnable command)メソッドを宣言します。
ExecutorServiceインターフェイスはさらに興味深いものです。作業の完了を管理するメソッドと、何らかの結果を返すメソッドがあります。そのメソッドを詳しく見てみましょう。
方法 | 説明 |
---|---|
void shutdown(); | このメソッドを呼び出すと、ExecutorServiceが停止します。すでに処理のために送信されたタスクはすべて完了しますが、新しいタスクは受け入れられません。 |
List<Runnable> shutdownNow(); |
このメソッドを呼び出すと、ExecutorServiceが停止します。Thread.interrupt は、処理のためにすでに送信されているすべてのタスクに対して呼び出されます。このメソッドは、キューに入れられたタスクのリストを返します。 このメソッドは、メソッドの呼び出し時に「進行中」であるすべてのタスクの完了を待ちません。 警告: このメソッドを呼び出すと、リソースがリークする可能性があります。 |
ブール値はShutdown(); | ExecutorService が停止しているかどうかを確認します。 |
ブール値 isTerminated(); | ExecutorServiceのシャットダウン後にすべてのタスクが完了した場合は true を返します。shutdown()またはshutdownNow() が呼び出されるまで、常にfalseを返します。 |
boolean awaitTermination(長いタイムアウト、TimeUnit 単位) は InterruptedException をスローします。 |
shutdown()メソッドが呼び出された後、次の条件のいずれかが満たされるまで、このメソッドは呼び出されたスレッドをブロックします。
すべてのタスクが完了した場合はtrueを返し、終了前にタイムアウトが経過した場合はfalseを返します。 |
<T> Future<T> submit(Callable<T> タスク); |
CallableタスクをExecutorServiceに追加し、 Futureインターフェイスを実装するオブジェクトを返します。 <T>は、渡されたタスクの結果の型です。 |
<T> Future<T> submit(実行可能なタスク、T 結果); |
RunnableタスクをExecutorServiceに追加し、 Futureインターフェイスを実装するオブジェクトを返します。 T結果パラメータは、結果のget()メソッドの呼び出しによって返されるものです。未来のオブジェクト。 |
Future<?> submit(実行可能なタスク); |
RunnableタスクをExecutorServiceに追加し、 Futureインターフェイスを実装するオブジェクトを返します。 結果として得られるFutureオブジェクトに対してget()メソッドを呼び出すと、null が返されます。 |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> タスク) は InterruptedException をスローします。 |
CallableタスクのリストをExecutorServiceに渡します。作業の結果を取得できる Future のリストを返します。このリストは、送信されたすべてのタスクが完了すると返されます。 メソッドの実行中にタスクコレクションが変更された場合、このメソッドの結果は未定義になります。 |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> タスク、長いタイムアウト、TimeUnit 単位) は InterruptedException をスローします。 |
CallableタスクのリストをExecutorServiceに渡します。作業の結果を取得できる Future のリストを返します。このリストは、渡されたすべてのタスクが完了したとき、またはメソッドに渡されたタイムアウトが経過した後のいずれか早い方で返されます。 タイムアウトが経過すると、未完了のタスクはキャンセルされます。 注: キャンセルされたタスクの実行が停止しない可能性があります (この副作用は例で確認します)。 メソッドの実行中にタスクコレクションが変更された場合、このメソッドの結果は未定義になります。 |
<T> T invokeAny(Collection<? extends Callable<T>> タスク) は、InterruptedException、ExecutionException をスローします。 |
CallableタスクのリストをExecutorServiceに渡します。例外 (存在する場合) をスローせずに完了したタスク (存在する場合) の 1 つの結果を返します。 メソッドの実行中にタスクコレクションが変更された場合、このメソッドの結果は未定義になります。 |
<T> T invokeAny(Collection<? extends Callable<T>> タスク、長いタイムアウト、TimeUnit 単位) は、InterruptedException、ExecutionException、TimeoutException をスローします。 |
CallableタスクのリストをExecutorServiceに渡します。メソッドに渡されたタイムアウトが経過する前に、例外をスローせずに完了したいずれかのタスク (存在する場合) の結果を返します。 メソッドの実行中にタスクコレクションが変更された場合、このメソッドの結果は未定義になります。 |
ExecutorServiceを操作する小さな例を見てみましょう。
import java.util.List;
import java.util.concurrent.*;
public class ExecutorServiceTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
MyRunnable task1 = new MyRunnable();
MyRunnable task2 = new MyRunnable();
MyRunnable task3 = new MyRunnable();
MyRunnable task4 = new MyRunnable();
MyRunnable task5 = new MyRunnable();
final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
System.out.println("Futures received");
// Stop the ExecutorService
executorService.shutdown();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executorService.isShutdown());
System.out.println(executorService.isTerminated());
}
public static class MyRunnable implements Callable<Void> {
@Override
public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("sleep 1: " + e.getMessage());
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("sleep 2: " + e.getMessage());
}
System.out.println("done");
return null;
}
}
}
出力:
完了
フューチャーを受信しました
スリープ 1: スリープが中断されました
スリープ 1: スリープが中断されました
完了
完了
true
true
各タスクは 5 秒間実行されます。2 つのスレッド用のプールを作成したため、出力の最初の 2 行は完全に意味を成します。
プログラムの開始から 6 秒後に、invokeAllメソッドがタイムアウトになり、結果がFuturesのリストとして返されます。これは、出力文字列Futures selectedからわかります。
最初の 2 つのタスクが完了すると、さらに 2 つのタスクが始まります。ただし、 invokeAllメソッドで設定されたタイムアウトが経過するため、これら 2 つのタスクを完了する時間がありません。彼らは「キャンセル」コマンドを受け取ります。そのため、出力にはsleep 1: sleep Interrupted という2 行が表示されます。
そして、doneを含むさらに 2 行が表示されます。これは、 invokeAllメソッドの説明時に述べた副作用です。
5 番目で最後のタスクは開始されることさえないため、出力にはそれに関する何も表示されません。
最後の 2 行は、 isShutdownメソッドとisTerminatedメソッドを呼び出した結果です。
この例をデバッグ モードで実行し、タイムアウト経過後のタスクのステータスを確認することも興味深いです ( executorService.shutdown();を使用して行にブレークポイントを設定します)。
2 つのタスクが正常に完了し、3 つのタスクが「キャンセル」されたことがわかります。
ScheduledExecutorService
エグゼキュータについての説明の締めくくりとして、 ScheduledExecutorServiceを見てみましょう。
4つの方法があります:
方法 | 説明 |
---|---|
public ScheduledFuture<?> スケジュール(実行可能なコマンド、長い遅延、TimeUnit 単位); | 渡されたRunnableタスクを、引数として指定された遅延後に 1 回実行するようにスケジュールします。 |
public <V> ScheduledFuture<V> スケジュール(Callable<V> 呼び出し可能、長い遅延、TimeUnit 単位); | 渡された呼び出し可能なタスクを、引数として指定された遅延の後に 1 回実行するようにスケジュールします。 |
public ScheduledFuture<?>scheduleAtFixedRate(実行可能なコマンド、長いinitialDelay、長い期間、TimeUnit単位); | 渡されたタスクの定期的な実行をスケジュールします。このタスクは、 initialDelayの後に初めて実行され、後続の各実行はperiodの後に開始されます。 |
public ScheduledFuture<?>scheduleWithFixedDelay(実行可能なコマンド、長いinitialDelay、長い遅延、TimeUnit単位); | 渡されたタスクの定期的な実行をスケジュールします。このタスクは、 initialDelayの後に初めて実行され、後続の各実行は遅延(前回の実行の完了と現在の実行の開始の間の期間) 後に開始されます。 |