Java Gold 17の並列処理対策|Thread・ExecutorService

並列処理は、タスクの定義・開始・完了待ち・終了処理を分けて読むことが出発点です。実行順序が保証される部分と、スケジューラに依存する部分を区別します。

Threadは生成・開始・待機を分ける

  • new Thread(...)はスレッドのオブジェクトを作ります。渡した処理を開始する操作はstart()です。
  • run()を直接呼ぶ場合は通常のメソッド呼び出しであり、新しいスレッドを開始しません。
  • join()は対象スレッドの終了を待ちます。sleep()は現在実行中のスレッドを一定時間待機させます。
  • 一度開始したThreadは、終了を待ってからでも再びstart()できません。

出力の問題では、まずjoin()などが保証する前後関係を見つけます。実行して一度同じ順序になっただけでは、毎回その順序になる証明にはなりません。基本の違いはJava SE 17のThread APIで確認できます。

ExecutorServiceとFutureの役割

ExecutorServiceはタスクの実行を管理します。submitが返すFutureでは結果や完了を確認できます。Runnable自身は値を返さず、Callable<V>はV型の値を返し、検査例外も宣言できます。

操作確認する点
Future.get()必要なら完了まで待ちます。引数のないgetに固定のタイムアウトはありません。
shutdown()新しいタスクを受け付けなくなります。投入済みのタスクは実行されますが、この呼び出し自体が全タスクの完了を待つわけではありません。
shutdownNow()実行中のタスクの停止を試みます。割込みに応答しない処理を含め、必ず即座に停止するという保証はありません。

Java 17では、ExecutorServiceを後のJavaバージョンの例と同じようにtry-with-resourcesで使えると考えないでください。対応バージョンはJava SE 17のExecutorService APIを基準にします。

例題2問:再開始とshutdown後のタスク

「開始できる回数」と「終了を要求した時点ですでに投入されているタスク」を確認して答えてください。

例題1:Threadを再開始できるか

次の処理が最後に表示する内容を選んでください。必要なimportは記述済みです。

Thread t = new Thread(() -> {});
t.start(); t.join();
try { t.start(); System.out.print("restarted"); }
catch (IllegalThreadStateException e) { System.out.print("one-start"); }

1つ選択してください

  • A. one-start
  • B. restarted
  • C. コンパイルエラー
  • D. IllegalStateException
例題1の正解・解説を見る

正解:A

正解は「one-start」です。

一度開始したThreadは、終了後でも再びstartできません。再実行には新しいThreadなどを使います。

  • A:一度開始したThreadは、終了後でも再びstartできません。再実行には新しいThreadなどを使います。
  • B:この結果にはなりません。一度開始したThreadは、終了後でも再びstartできません。再実行には新しいThreadなどを使います。
  • C:この結果にはなりません。一度開始したThreadは、終了後でも再びstartできません。再実行には新しいThreadなどを使います。
  • D:この結果にはなりません。一度開始したThreadは、終了後でも再びstartできません。再実行には新しいThreadなどを使います。

確認するポイント:joinで終了を待ってもThreadが新品の状態へ戻るわけではありません。

仕様を確認するための公式資料:

問題ID:jg17-08ad887b7923d2 · この問題の訂正を連絡する

例題2:shutdown後の既存タスク

次の処理が最後に表示する内容を選んでください。必要なimportは記述済みです。

var pool = Executors.newSingleThreadExecutor();
try {
    var a = pool.submit(() -> "A");
    var b = pool.submit(() -> "B");
    pool.shutdown();
    System.out.print(a.get() + b.get());
} finally { pool.shutdown(); }

1つ選択してください

  • A. A
  • B. RejectedExecutionException
  • C. TimeoutException
  • D. AB
例題2の正解・解説を見る

正解:D

正解は「AB」です。

shutdownは新規投入を拒否しますが、既に投入したタスクは実行されます。引数なしgetに固定のタイムアウトはありません。

  • A:この結果にはなりません。shutdownは新規投入を拒否しますが、既に投入したタスクは実行されます。引数なしgetに固定のタイムアウトはありません。
  • B:この結果にはなりません。shutdownは新規投入を拒否しますが、既に投入したタスクは実行されます。引数なしgetに固定のタイムアウトはありません。
  • C:この結果にはなりません。shutdownは新規投入を拒否しますが、既に投入したタスクは実行されます。引数なしgetに固定のタイムアウトはありません。
  • D:shutdownは新規投入を拒否しますが、既に投入したタスクは実行されます。引数なしgetに固定のタイムアウトはありません。

確認するポイント:shutdownNowとの違いと、タスク投入のタイミングを確認します。

仕様を確認するための公式資料:

問題ID:jg17-54c1c031c5f915 · この問題の訂正を連絡する

並列処理の61問で復習する

問題演習の「出題分野」で「並列処理」を選ぶと、61問の演習ができます。61問は本サイトの収録数で、試験の出題比率ではありません。

基本を確認したら、同期・ロック・Atomic・並列コレクションへ進みます。「競合を防げる範囲」「可視性」「処理全体が不可分か」を分けて説明できるか確かめましょう。Streamを並列化する問題はStream APIの要点と併せて復習できます。

次に取り組む学習

問題は本試験の過去問ではなく、Java SE 17を学ぶためのオリジナル練習教材です。編集・訂正の方針をご確認ください。