Java Language
MultiThreadedアプリケーションでThreadPoolExecutorを使用する
サーチ…
前書き
パフォーマンスとデータ駆動型のアプリケーションを作成する場合、時間のかかるタスクを非同期で完了し、複数のタスクを同時に実行すると非常に役に立ちます。このトピックでは、ThreadPoolExecutorsを使用して複数の非同期タスクを同時に完了する概念を紹介します。
実行可能クラス・インスタンスを使用して戻り値が不要な非同期タスクの実行
一部のアプリケーションでは、定期的にトリガされ、割り当てられたタスクの完了時に返される値のタイプを返す必要のない、いわゆる "Fire&Forget"タスクを作成することがあります(古い一時ファイルのパージ、ログのローテーション、状態)。
この例では、Runnableインターフェイスを実装するクラスと、main()メソッドを含むクラスの2つのクラスを作成します。
AsyncMaintenanceTaskCompleter.java
import lombok.extern.java.Log;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Log
public class AsyncMaintenanceTaskCompleter implements Runnable {
private int taskNumber;
public AsyncMaintenanceTaskCompleter(int taskNumber) {
this.taskNumber = taskNumber;
}
public void run() {
int timeout = ThreadLocalRandom.current().nextInt(1, 20);
try {
log.info(String.format("Task %d is sleeping for %d seconds", taskNumber, timeout));
TimeUnit.SECONDS.sleep(timeout);
log.info(String.format("Task %d is done sleeping", taskNumber));
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
}
}
AsyncExample1
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AsyncExample1 {
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i < 10; i++){
executorService.execute(new AsyncMaintenanceTaskCompleter(i));
}
executorService.shutdown();
}
}
AsyncExample1.main()を実行すると、次のように出力されます。
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 8 is sleeping for 18 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 6 is sleeping for 4 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 2 is sleeping for 6 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 3 is sleeping for 4 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 9 is sleeping for 14 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 4 is sleeping for 9 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 5 is sleeping for 10 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 0 is sleeping for 7 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 1 is sleeping for 9 seconds
Dec 28, 2016 2:21:03 PM AsyncMaintenanceTaskCompleter run
INFO: Task 7 is sleeping for 8 seconds
Dec 28, 2016 2:21:07 PM AsyncMaintenanceTaskCompleter run
INFO: Task 6 is done sleeping
Dec 28, 2016 2:21:07 PM AsyncMaintenanceTaskCompleter run
INFO: Task 3 is done sleeping
Dec 28, 2016 2:21:09 PM AsyncMaintenanceTaskCompleter run
INFO: Task 2 is done sleeping
Dec 28, 2016 2:21:10 PM AsyncMaintenanceTaskCompleter run
INFO: Task 0 is done sleeping
Dec 28, 2016 2:21:11 PM AsyncMaintenanceTaskCompleter run
INFO: Task 7 is done sleeping
Dec 28, 2016 2:21:12 PM AsyncMaintenanceTaskCompleter run
INFO: Task 4 is done sleeping
Dec 28, 2016 2:21:12 PM AsyncMaintenanceTaskCompleter run
INFO: Task 1 is done sleeping
Dec 28, 2016 2:21:13 PM AsyncMaintenanceTaskCompleter run
INFO: Task 5 is done sleeping
Dec 28, 2016 2:21:17 PM AsyncMaintenanceTaskCompleter run
INFO: Task 9 is done sleeping
Dec 28, 2016 2:21:21 PM AsyncMaintenanceTaskCompleter run
INFO: Task 8 is done sleeping
Process finished with exit code 0
注記:上記の出力にはいくつか注意すべき点がありますが、
- タスクは予測可能な順序で実行されませんでした。
- 各タスクは(擬似的に)ランダムな時間だけスリープしていたため、呼び出された順に必ずしも完了するとは限りませんでした。
呼び出し可能クラス・インスタンスを使用して戻り値が必要な非同期タスクの実行
長時間実行しているタスクを実行し、完了したらそのタスクの結果を使用する必要があることがよくあります。
この例では、2つのクラスを作成します.1つはCallable <T>インターフェース(ここでTは返すタイプ)を実装するものと、main()メソッドを含むものです。
AsyncValueTypeTaskCompleter.java
import lombok.extern.java.Log;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Log
public class AsyncValueTypeTaskCompleter implements Callable<Integer> {
private int taskNumber;
public AsyncValueTypeTaskCompleter(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public Integer call() throws Exception {
int timeout = ThreadLocalRandom.current().nextInt(1, 20);
try {
log.info(String.format("Task %d is sleeping", taskNumber));
TimeUnit.SECONDS.sleep(timeout);
log.info(String.format("Task %d is done sleeping", taskNumber));
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
return timeout;
}
}
AsyncExample2.java
import lombok.extern.java.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Log
public class AsyncExample2 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++){
Future<Integer> submittedFuture = executorService.submit(new AsyncValueTypeTaskCompleter(i));
futures.add(submittedFuture);
}
executorService.shutdown();
while(!futures.isEmpty()){
for(int j = 0; j < futures.size(); j++){
Future<Integer> f = futures.get(j);
if(f.isDone()){
try {
int timeout = f.get();
log.info(String.format("A task just completed after sleeping for %d seconds", timeout));
futures.remove(f);
} catch (InterruptedException | ExecutionException e) {
log.warning(e.getMessage());
}
}
}
}
}
}
AsyncExample2.main()を実行すると、次のように出力されます。
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 7 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 8 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 2 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 1 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 4 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 9 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 0 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 6 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 5 is sleeping
Dec 28, 2016 3:07:15 PM AsyncValueTypeTaskCompleter call
INFO: Task 3 is sleeping
Dec 28, 2016 3:07:16 PM AsyncValueTypeTaskCompleter call
INFO: Task 8 is done sleeping
Dec 28, 2016 3:07:16 PM AsyncExample2 main
INFO: A task just completed after sleeping for 1 seconds
Dec 28, 2016 3:07:17 PM AsyncValueTypeTaskCompleter call
INFO: Task 2 is done sleeping
Dec 28, 2016 3:07:17 PM AsyncExample2 main
INFO: A task just completed after sleeping for 2 seconds
Dec 28, 2016 3:07:17 PM AsyncValueTypeTaskCompleter call
INFO: Task 9 is done sleeping
Dec 28, 2016 3:07:17 PM AsyncExample2 main
INFO: A task just completed after sleeping for 2 seconds
Dec 28, 2016 3:07:19 PM AsyncValueTypeTaskCompleter call
INFO: Task 3 is done sleeping
Dec 28, 2016 3:07:19 PM AsyncExample2 main
INFO: A task just completed after sleeping for 4 seconds
Dec 28, 2016 3:07:20 PM AsyncValueTypeTaskCompleter call
INFO: Task 0 is done sleeping
Dec 28, 2016 3:07:20 PM AsyncExample2 main
INFO: A task just completed after sleeping for 5 seconds
Dec 28, 2016 3:07:21 PM AsyncValueTypeTaskCompleter call
INFO: Task 5 is done sleeping
Dec 28, 2016 3:07:21 PM AsyncExample2 main
INFO: A task just completed after sleeping for 6 seconds
Dec 28, 2016 3:07:25 PM AsyncValueTypeTaskCompleter call
INFO: Task 1 is done sleeping
Dec 28, 2016 3:07:25 PM AsyncExample2 main
INFO: A task just completed after sleeping for 10 seconds
Dec 28, 2016 3:07:27 PM AsyncValueTypeTaskCompleter call
INFO: Task 6 is done sleeping
Dec 28, 2016 3:07:27 PM AsyncExample2 main
INFO: A task just completed after sleeping for 12 seconds
Dec 28, 2016 3:07:29 PM AsyncValueTypeTaskCompleter call
INFO: Task 7 is done sleeping
Dec 28, 2016 3:07:29 PM AsyncExample2 main
INFO: A task just completed after sleeping for 14 seconds
Dec 28, 2016 3:07:31 PM AsyncValueTypeTaskCompleter call
INFO: Task 4 is done sleeping
Dec 28, 2016 3:07:31 PM AsyncExample2 main
INFO: A task just completed after sleeping for 16 seconds
注記の観察:
上記の出力にはいくつか注意すべきことがありますが、
- ExecutorService.submit()を呼び出すたびに、後で使用するためにリストに格納されたFutureのインスタンスが返されました
- FutureにはisDone()というメソッドがあり、戻り値をチェックしようとする前にタスクが完了しているかどうかをチェックできます。将来未完了のFuture.get()メソッドを呼び出すと、タスクが完了するまで現在のスレッドがブロックされ、タスクを非同期的に実行することによって得られる多くの利点が無効になる可能性があります。
- Futureオブジェクトの戻り値をチェックする前に、executorService.shutdown()メソッドが呼び出されました。これは必須ではありませんが、これが可能であることを示すためにこの方法で行われました。 executorService.shutdown()メソッドは、既にExecutorServiceに送信されたタスクの完了を防ぎませんが、新しいタスクがキューに追加されるのを防ぎます。
Lambdaを使用した非同期タスクのインライン定義
優れたソフトウェア設計ではコードの再利用性が最大化されることがありますが、ラムダ式を使用してコード内で非同期タスクをインラインで定義して、コードの可読性を最大化すると便利な場合があります。
この例では、main()メソッドを含む単一のクラスを作成します。このメソッドでは、ラムダ式を使用してCallableとRunnable <T>のインスタンスを作成して実行します。
AsyncExample3.java
import lombok.extern.java.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
@Log
public class AsyncExample3 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<Integer>> futures = new ArrayList<>();
for(int i = 0; i < 5; i++){
final int index = i;
executorService.execute(() -> {
int timeout = getTimeout();
log.info(String.format("Runnable %d has been submitted and will sleep for %d seconds", index, timeout));
try {
TimeUnit.SECONDS.sleep(timeout);
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
log.info(String.format("Runnable %d has finished sleeping", index));
});
Future<Integer> submittedFuture = executorService.submit(() -> {
int timeout = getTimeout();
log.info(String.format("Callable %d will begin sleeping", index));
try {
TimeUnit.SECONDS.sleep(timeout);
} catch (InterruptedException e) {
log.warning(e.getMessage());
}
log.info(String.format("Callable %d is done sleeping", index));
return timeout;
});
futures.add(submittedFuture);
}
executorService.shutdown();
while(!futures.isEmpty()){
for(int j = 0; j < futures.size(); j++){
Future<Integer> f = futures.get(j);
if(f.isDone()){
try {
int timeout = f.get();
log.info(String.format("A task just completed after sleeping for %d seconds", timeout));
futures.remove(f);
} catch (InterruptedException | ExecutionException e) {
log.warning(e.getMessage());
}
}
}
}
}
public static int getTimeout(){
return ThreadLocalRandom.current().nextInt(1, 20);
}
}
注記の観察:
上記の出力にはいくつか注意すべきことがありますが、
- ラムダ式は、変数が定義されているスコープで使用可能な変数とメソッドにアクセスできますが、すべての変数はラムダ式内で使用するために最終的(または効果的に最終的)でなければなりません。
- ラムダ式がCallableかRunnable <T>かを明示的に指定する必要はなく、戻り値の型は戻り値の型によって自動的に推定されます。