追記:
Java 19 から ExecutorService#close を使えばよさそうです。
コード
ExecutorServiceを停止するユーティリティ的な(Kotlin)
object ExecutorServiceUtils {
val log by logger()
/**
* [ExecutorService]を正しく終了する。
* 実行中のタスクがある場合は終了を待機する。
* 一定時間待機しても終了しないタスクがある場合はキャンセル(interrupt)しようとする。
*
* @param pool [ExecutorService]
* @param waitTimeout 実行中のタスクの待機時間
* @param cancelTimeout 実行中のタスクをキャンセルするときの待機時間
*/
fun shutdownAndAwaitTermination(
pool: ExecutorService,
waitTimeout: Duration,
cancelTimeout: Duration
) {
pool.shutdown() // 新しいタスクの受付を停止
try {
// 実行中のタスクの終了を待機
if (!pool.awaitTermination(waitTimeout.toMillis(), TimeUnit.MILLISECONDS)) {
// 待機がタイムアウトしたとき
log.warn("一定時間待機したが終了しないタスクがあるので、実行中のタスクをキャンセルする")
pool.shutdownNow() // 実行中のタスクをキャンセルする
// 実際に停止するまでもう一回待機
if (!pool.awaitTermination(cancelTimeout.toMillis(), TimeUnit.MILLISECONDS)) {
// 待機がタイムアウトしたとき
log.warn("実行中のタスクをキャンセルできない")
}
}
} catch (ie: InterruptedException) {
// 現在のスレッドが割り込みされたとき、実行中のタスクを(もう一度)キャンセルしておく
pool.shutdownNow()
// interruptedのステータスを保持
Thread.currentThread().interrupt()
}
}
}
解説
shutdown()
シャットダウンを開始する
- 新しいタスクを受け付けないようになる
- 実行中のタスクがある場合はそのまま実行される
- 実行中のタスクの完了を待機しない。待機したい場合は
awaitTermination
を使う
awaitTermination(long, TimeUnit)
実行中のタスクが終了するまで待機する(処理をブロックする)
- 実行中のタスクがない場合は特になにもしない
- 指定したタイムアウト時間経っても終了しない場合は
false
を返す - 待機している間に現在のスレッドに対して割り込み(interrupt)があった場合は
InterruptedException
がスローされる
shutdownNow()
実行中のタスクの停止(キャンセル)を試みる
- タスクを停止できるとは限らない(
Thread.interrupt()
するだけなので。タスク側の処理を停止可能なように作っておく必要がある) - 停止完了まで待機しない。待機したい場合は
awaitTermination
を使う
このサンプルコードの注意点
1つのアプリケーションの中で複数のExecutorService
を扱う場合、サンプルコードのログ出力ではどのExecutorService
に関するログなのか判断できない。