ExecutorServiceを使って行う具体的な処理の詳細
著者:Simon Roberts、Mikalai Zaikin
2019年8月26日
過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」というレベルは、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。
設問(上級者向け):RunnableおよびCallableを使ってワーカー・スレッドを作成し、ExecutorServiceを使って複数のタスクを同時に実行したいと思います。次のクラスについて:
class Logger implements Runnable {
String msg;
public Logger(String msg) {
this.msg = msg;
}
public void run() {
System.out.print(msg);
}
}
さらに、次のコード部分について:
Stream<Logger> s = Stream.of(
new Logger("Error "),
new Logger("Warning "),
new Logger("Debug "));
ExecutorService es =
Executors.newCachedThreadPool();
s.sequential().forEach(l -> es.execute(l));
es.shutdown();
es.awaitTermination(10, TimeUnit.SECONDS);
すべてのimport文(表示されていません)が適切に構成されており、コードがコンパイルできると仮定した場合、出力される可能性があるものはどれですか。2つ選んでください。
- Error Debug Warning
- Error Warning Debug
- Error Error Debug
- Error Debug
解答:試験対象には、ExecutorsクラスおよびExecutorServiceインタフェースに関連するトピックが含まれています。Executorsクラスが提供するExecutorServiceインタフェースを実装したスレッド・プールも同様です。
Javaの初期のバージョンでは、スレッドの作成や管理はプログラマーの責任でした。また、スレッドは作成が高価であり、限られた、カーネルレベルのリソースでもあるため、「スレッド・プール」を作って、少数のスレッドで独立した小さなジョブを数多く処理できるようにするのが一般的でした。スレッド・プールという考え方は、小さなバックグラウンド・ジョブごとにスレッドを作り、各ジョブの完了後にそのスレッドを破棄するという方法に代わるものです。スレッド・プールでは、少数のスレッドを作って構成し、まとまった仕事(たとえば、Runnableオブジェクト)をスレッドに渡せるようにします。仕事がないスレッドのうち最初のものがジョブを引き受けて実行し、そのジョブが完了すると、そのスレッドは別のジョブを探します。
スレッド・プールは、Java 5でJavaのAPIの一部になりました。ExecutorインタフェースおよびExecutorServiceインタフェースは、スレッド・プールと、スレッド・プールがサポートする操作を汎用化したものです。また、静的ファクトリ・メソッドを含むクラスから、多くのExecutorService実装のインスタンスを生成できます。そのクラスがExecutorsです。java.util.concurrentパッケージには、これら3つの型のほか、多数の高水準なクラスやインタフェースが含まれています。こういったクラスやインタフェースは、並行プログラミングでよく生じる問題に対処しようとする開発者に活用してもらうことを意図したものです。
ベース・インタフェースのExecutorは、Runnableを実装したタスクを実行できます。通常は、ExecutorServiceを使うことが多いでしょう。これはExecutorのサブインタフェースです。ExecutorServiceには、Callableインタフェースを実装したタスクを処理する機能と、スレッド・プールの停止を制御する機能が追加されています。Callableインタフェースを使うことで、マネージャ・タスクから非同期的にタスクの結果を取得できるようになります。多くの場合、このマネージャ・タスクのコードが最初にジョブを送信しますが、必ずそうであるとは限りません。
ExecutorやExecutorServiceの実装は、送信された仕事を実行するために特定の戦略を使う必要はありません。実装の中には、固定サイズのプールを使って同時実行するものもあります。その場合、新しい仕事は、スレッドが利用できるようになるまで待機します。ワークロードが増加した際により多くのスレッドを開始し、需要が少なくなった際にスレッドをクリーンアップする実装もあります。また、1つのスレッドを使って単純にジョブを逐次処理する実装もあります。すべては個々の実装次第です。そのため、プログラマーはアプリケーションのアーキテクチャのニーズに適した実装を注意深く選択する必要があります。Executorsクラスの3つのファクトリ・メソッドでは、前述の動作が生成されます。
- newFixedThreadPool
- newCachedThreadPool
- newSingleThreadExecutor
最初の2つは複数のワーカー・スレッドを使うプールを作成しますが、newSingleThreadExecutorはすべてのタスクを1つのバックグラウンド・スレッドで次々に実行するサービスを作成します。
本設問のコードでは、キャッシュド・スレッド・プールを使っています。このタイプのExecutorServiceは、必要に応じて新しいワーカー・スレッドを生成し、一定期間使用されなかったスレッドをクリーンアップします。しかし、キャッシュド・スレッド・プールには、作成できるスレッドの最大数に制限がないという重大な欠点があります。この動作により、高負荷時にリソース消費量が高くなり、パフォーマンスが低下する可能性があります。
設問のコードで作成しているプールには複数のスレッドがあることを踏まえれば、プールに送信されたジョブはおそらく同時実行されると考えることができます。そのため、開始される順番がどうであれ、相対的な処理の進み具合を予測することはできません。つまり、出力されるメッセージはどんな順番にもなり得るということです。ここから、選択肢AとBがいずれも正解であることがわかります。
ExecutorServiceが、送信された各ジョブを実行するのは、多くても1回です。状況によっては、ジョブが実行されないことや、完了前に停止されることもあるかもしれません。しかし、ジョブが複数回実行されることはありません。つまり、メッセージが重複することは絶対にありません。よって、選択肢Cは誤りです。
ExecutorServiceに対してshutdownメソッドを呼ぶと、新しいジョブのリクエストは拒否されるようになりますが、実行は最後のジョブが完了するまで続きます。そのため、設問のコードでは、3つのメッセージのうちいくつかが表示されないということはありません。以上より、選択肢Dも誤りであることがわかります。
1点補足しておきます。10秒以内にシャットダウンが完了しない場合、コードは末尾に到達することから、選択肢Dは正解かもしれないと考える方もいるかもしれません。3つのメッセージが確実に表示されると断言することはできるのでしょうか。
ここで、いくつかの見解が関連してきます。まず、このジョブが完了するまでに10秒かかる可能性は非常に低いということです。また、2つの選択肢(AとB)が明らかに正解であることを考えれば、このような起こりそうもないことは除外できます。
もちろん、ホストで極端な状況が発生すれば、ジョブが10秒以内に完了しない可能性もあります。そこから、選択肢Dはやはり正解かもしれないと思うかもしれません。たとえば、設問のコードが起動した瞬間に、OSのアップデートのインストールが始まった場合を考えてみてください。設問のコードには、仮想マシンが強制シャットダウン中であることを示す内容は含まれていないことに注意してください。このスレッド・プールのスレッドは非デーモン・スレッドであるため、ジョブが完了するまで仮想マシンはシャットダウンされません。そのため、プログラムの実行が許可されれば、3つのメッセージが表示されます。
正解は選択肢AとBです。
Java Magazine December 2019の他の記事
プロパティベース・テストを習得するArquillian:簡単なJakarta EEテスト
ArchUnitでアーキテクチャの単体テストを行う
新しいJava Magazine
作ってみよう:自分だけのテキスト・エディタ(パート1)
クイズに挑戦:Collectorsの使用(上級者向け)
クイズに挑戦:ループ構造の比較(中級者向け)
クイズに挑戦:ラッパー・クラス(中級者向け)
書評:Core Java, 11th Ed. Volumes 1 and 2
![]() |
Simon RobertsSimon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO’Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。 |
![]() |
Mikalai ZaikinMikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。 |


