Javaの並列処理において、Executor
フレームワークは非常に重要な役割を果たします。
似たようなインタフェースやらメソッドやらが多くてごちゃごちゃしてたExecutor
フレームワークですが、この度完全に理解したのでまとめます。
1. Executorフレームワークの概要
Executor
フレームワークは、スレッドの直接管理を避け、タスク実行を簡素化するための仕組みを提供します。スレッドプールを活用することで、スレッドの再利用やリソース管理を効率化します。
主な利点
- スレッド管理の簡略化: 手動でスレッドを作成・破棄する必要なし!
- リソース効率の向上: スレッドプールの利用でスレッドを再利用!
- 高度なタスク管理: スケジュール実行や非同期処理の管理が簡単!
2. Executorインターフェース
定義
Executor
は基本的なタスク実行のためのインターフェースです。
public interface Executor {
void execute(Runnable command);
}
-
使い方:
-
execute
メソッドにRunnable
を渡してタスクを非同期実行します。渡すRunnable
はラムダ式で記述してもOKです。
-
サンプルコード
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
// ユーティリティクラスのExecutors(後述)でスレッドを生成
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(() -> System.out.println("Task executed"));
}
}
Task executed
3. ExecutorServiceインターフェース
ExecutorService
はExecutor
を拡張し、タスクの管理やスレッドプール操作を可能にするインターフェースです。
主なメソッド
-
submit()
-
Runnable
またはCallable
タスクをスレッドプールに提出します。 -
Future
オブジェクトを返す。
-
-
shutdown()
- スレッドプールの正常終了を指示します。
-
shutdownNow()
- 実行中のタスクを強制終了します。
-
invokeAll()
- 複数のタスクを同時に実行し、すべて完了するまで待機。
-
invokeAny()
- 複数のタスクのうち、最初に完了したタスクの結果を返します。
もちろん、execute()
も持っています。
サンプルコード: submit
とshutdown
の使用例
import java.util.concurrent.*;
public class ExecutorServiceExample {
public static void main(String[] args) {
// ユーティリティクラスのExecutors(後述)でスレッドを生成
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task1 = () -> System.out.println("Task 1 executed");
Runnable task2 = () -> System.out.println("Task 2 executed");
executor.submit(task1);
executor.submit(task2);
executor.shutdown();
}
}
Task 1 executed
Task 2 executed
4. FutureインターフェースとCallableインターフェース
Future
とCallable
は非同期タスクを管理するためのインターフェースです。
-
Callable
: タスクを記述するためのインターフェース。Runnable
と異なり、戻り値を返すことができます。call()
を実装します。 -
Future
: 非同期タスクの結果を取得したり、タスクのキャンセルを行うためのインターフェース。
主なメソッド
-
get()
- タスクの結果を取得します。
- タスクが完了するまでブロッキングします。
- 引数に
(int time, TimeUnit)
を渡すことで、その時間内にスレッドの処理が完了しなかった場合、TimeoutExecption
をスローします。
-
cancel(boolean mayInterruptIfRunning)
- タスクをキャンセルします。
- 実行中のタスクを中断する場合は
true
を指定。
-
isDone()
- タスクが完了しているかどうかを返します。
-
isCancelled()
- タスクがキャンセルされたかどうかを返します。
サンプルコード: タスクの実行とキャンセル
import java.util.concurrent.*;
public class FutureExample {
public static void main(String[] args) {
// ユーティリティクラスのExecutors(後述)でスレッドを生成
ExecutorService executor = Executors.newSingleThreadExecutor();
// taskをRunnableではなくCallableで定義
Callable<String> task = () -> {
// 10秒待機
Thread.sleep(10000);
return "Task completed!";
};
// Futureで結果を受け取る
Future<String> future = executor.submit(task);
System.out.println("Task submitted.");
try {
// タスクが完了しているかチェック
if (!future.isDone()) {
System.out.println("Task is still running...");
}
// タスクの結果を取得 5秒以内に完了しなければ例外をスロー
String result = future.get(5, TimeUnit.SECONDS);
System.out.println("Result: " + result);
} catch (TimeoutException e) {
System.out.println("Task timed out. Cancelling...");
future.cancel(true); // タスクをキャンセル
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
Task submitted.
Task is still running...
Task timed out. Cancelling...
5. ScheduledExecutorServiceインターフェース
ScheduledExecutorService
は、一定の遅延後や周期的にタスクを実行するためのインターフェースです。タスクはRunnable
またはCallable
として渡すことができます。
主なメソッド
-
schedule(Runnable command, long delay, TimeUnit unit)
- 指定した遅延後にタスクを実行します。
- 戻り値は
ScheduledFuture<?>
。null
になりますが、ScheduledFuture<?>
を通じてタスクの完了状態を管理できます。
-
schedule(Callable<V> callable, long delay, TimeUnit unit)
- 指定した遅延後にタスクを実行し、結果を返します。
- 戻り値は
ScheduledFuture<V>
。
-
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
- 指定した初期遅延後、固定間隔でタスクを繰り返し実行します。
-
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
- 指定した初期遅延後、各タスクの終了時から一定時間遅延して次のタスクを実行します。
サンプルコード: 周期的タスクの実行
import java.util.concurrent.*;
public class ScheduledExecutorExample {
public static void main(String[] args) {
// ユーティリティクラスのExecutors(後述)でスレッドを生成
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Task executed at " + System.currentTimeMillis());
scheduler.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
try {
Thread.sleep(10000); // 10秒間実行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
scheduler.shutdown();
}
}
}
Task executed at 1647250800000
Task executed at 1647250803000
Task executed at 1647250806000
Task executed at 1647250809000
6. Executorsユーティリティクラス
Executors
はExecutor
、ExecutorService
、ScheduledExecutorService
インターフェースを実装した具体的なクラス(例: ThreadPoolExecutor
、ScheduledThreadPoolExecutor
)を簡単に生成するためのユーティリティクラスです。
主なメソッド
-
newFixedThreadPool(int nThreads)
- 固定サイズのスレッドプールを作成。
-
newCachedThreadPool()
- 必要に応じてスレッドを生成し、再利用するスレッドプールを作成。
-
newSingleThreadExecutor()
- シングルスレッドのExecutorを作成。
-
newScheduledThreadPool(int corePoolSize)
- スケジュール実行可能なスレッドプールを作成。
サンプルコード: newFixedThreadPool
の使用例
import java.util.concurrent.*;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
final int taskNumber = i;
executor.execute(() -> {
System.out.println("Task " + taskNumber + " executed by " + Thread.currentThread().getName());
});
}
executor.shutdown();
}
}
Task 1 executed by pool-1-thread-1
Task 2 executed by pool-1-thread-2
Task 3 executed by pool-1-thread-3
Task 4 executed by pool-1-thread-1
Task 5 executed by pool-1-thread-2
7. ThreadPoolExecutorインターフェース
ThreadPoolExecutor
は、スレッドプールを詳細にカスタマイズするためのクラスです。
主なコンストラクタ
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue)
-
corePoolSize
: プール内の基本スレッド数。 -
maximumPoolSize
: プール内の最大スレッド数。 -
keepAliveTime
: アイドル状態のスレッドをプールに保持する時間。 -
workQueue
: タスクを保持するキュー。
サンプルコード: カスタムスレッドプール
import java.util.concurrent.*;
public class CustomThreadPoolExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2)
);
for (int i = 1; i <= 6; i++) {
final int taskNumber = i;
executor.execute(() -> {
System.out.println("Task " + taskNumber + " executed by " + Thread.currentThread().getName());
});
}
executor.shutdown();
}
}
Task 1 executed by pool-1-thread-1
Task 2 executed by pool-1-thread-2
Task 3 executed by pool-1-thread-3
Task 4 executed by pool-1-thread-4
Task 5 executed by pool-1-thread-1
Task 6 executed by pool-1-thread-2
8. execute()
・submit()
・schedule()
の整理
特徴 | execute() |
submit() |
schedule() |
---|---|---|---|
引数 | Runnable |
Runnable または Callable
|
Runnable または Callable
|
戻り値 | void |
Future |
ScheduledFuture |
結果の取得 | 不可 |
Future を使用して取得可能 |
ScheduledFuture を使用して取得可能 |
例外の処理 | スレッドプールに通知される |
Future.get() でExecutionException として取得可能 |
ScheduledFuture.get() でExecutionException として取得可能 |
遅延実行 | 不可 | 不可 | 指定した遅延時間後にタスクを実行可能 |
用途 | 単純な非同期タスクの実行 | 結果取得やタスクの完了状態の追跡が必要な場合 | 遅延実行またはスケジュールタスクを実行したい場合 |
9. まとめ
Executor
└── ExecutorService
├── ScheduledExecutorService
└── AbstractExecutorService (抽象クラス)
└── ThreadPoolExecutor (具体的実装)
Executors (ユーティリティクラス)
-
Executor
フレームワークは、スレッド管理を簡略化し、効率的なタスク実行を可能にします。 -
ExecutorService
によってスレッドプールを活用して非同期タスクを柔軟に管理できます。 -
ScheduledExecutorService
によって周期的タスクや遅延タスクを実行できます。 -
Executors
ユーティリティクラスによってExecutor
、ExecutorService
、ScheduledExecutorService
インターフェースを実装したクラスのインスタンスを簡単に生成できます。 -
ThreadPoolExecutor
によってより高度な制御が可能になります。