はじめに
Java Gold (SE 11 / SE 17) 試験における 「並列処理」章 は、Stream APIに次いで出題比率が高い分野です。中でも ScheduledExecutorService は、scheduleAtFixedRate と scheduleWithFixedDelay の挙動の違いや、例外発生時の振る舞いなど 細かい仕様を問う問題 が頻繁に出題されます。
:::
1. 位置付け ── Executor階層での立ち位置
まず ScheduledExecutorService がどこに属するインタフェースかを整理します。試験ではインタフェースの継承関係が問われることがあります。
Executor ← execute(Runnable) のみ
└─ ExecutorService ← submit, shutdown, invokeAll など
└─ ScheduledExecutorService ← schedule, scheduleAtFixedRate など
試験で覚えておくこと
-
ScheduledExecutorServiceはExecutorServiceを継承している - したがって
submit()/shutdown()/invokeAll()などもすべて使える -
Executorはexecute(Runnable)しか持たない最上位インタフェース
2. インスタンス生成 ── Executorsファクトリメソッド
インスタンスは直接コンストラクタを呼ばず、Executors クラスのファクトリメソッド で生成します。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
// スレッド数を指定
ScheduledExecutorService s1 = Executors.newScheduledThreadPool(2);
// シングルスレッド版
ScheduledExecutorService s2 = Executors.newSingleThreadScheduledExecutor();
試験で混同しやすいファクトリメソッド
| メソッド | 戻り値 | 用途 |
|---|---|---|
newFixedThreadPool(n) |
ExecutorService |
固定サイズのプール(スケジュール不可) |
newCachedThreadPool() |
ExecutorService |
必要に応じて伸縮(スケジュール不可) |
newSingleThreadExecutor() |
ExecutorService |
1スレッドのみ(スケジュール不可) |
newScheduledThreadPool(n) |
ScheduledExecutorService |
スケジュール実行可 |
newSingleThreadScheduledExecutor() |
ScheduledExecutorService |
1スレッドでスケジュール実行 |
newScheduledThreadPool と newSingleThreadScheduledExecutor だけがスケジュール機能を持つ。他のファクトリメソッドで返る ExecutorService に対して schedule() を呼ぼうとするとコンパイルエラー。
3. 3つのscheduleメソッド ── 挙動の違いを正確に押さえる
JavaGoldで最も問われるのがこの3メソッドの使い分けです。
メソッドシグネチャ一覧
// ① 一度だけ実行(Runnable版)
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
// ① 一度だけ実行(Callable版)
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
// ② 固定レート(開始時刻を基準)
ScheduledFuture<?> scheduleAtFixedRate(
Runnable command, long initialDelay, long period, TimeUnit unit);
// ③ 固定遅延(前タスクの終了時刻を基準)
ScheduledFuture<?> scheduleWithFixedDelay(
Runnable command, long initialDelay, long delay, TimeUnit unit);
戻り値はすべて ScheduledFuture<?>(Callable版だけ ScheduledFuture<V>)
void ではない。戻り値で cancel() や isDone() が呼べる。試験で「戻り値は何か」が問われる。
scheduleAtFixedRate vs scheduleWithFixedDelay ── 決定的な違い
ここが一番よく試験で出ます。図で理解しておきましょう。
【scheduleAtFixedRate(task, 0, 5s)】 ── 開始時刻が5秒間隔
時刻: 0 5 10 15
タスク: [==実行==][=実行=][==実行==][実行]
↑ ↑ ↑ ↑
開始 開始 開始 開始 ← 開始から開始までが period
【scheduleWithFixedDelay(task, 0, 5s)】 ── 終了〜開始が5秒間隔
時刻: 0 8 13 21
タスク: [==実行==] 5s [==実行==] 5s [==実行==]
↑ ↑ ↑
待機 待機 待機 ← 終了から開始までが delay
重要な落とし穴: scheduleAtFixedRate でタスクが period より長くかかる場合
仕様上、前のタスクの実行が終わってから次のタスクが開始される(並列実行はされない)。
つまりタスクが10秒かかるのに period=5秒 を指定しても、5秒おきには実行されず「終わり次第すぐ次」になる。
公式ドキュメントより:
If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.
─ ScheduledExecutorService JavaDoc
4. 【最重要】例外発生時の挙動
JavaGoldで ほぼ必ず出題される のがこのポイントです。
定期実行タスクで未捕捉の例外が発生すると、以降の実行は停止する
公式ドキュメントにも明記されている動作:
- タスクが例外をスロー → そのタスクの
ScheduledFutureは完了状態になる - 以降の定期実行は抑制される(停止する)
- 例外が起きたことはプログラム側には自動的に通知されない(気付かないまま止まる)
危険なコード例
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
System.out.println("Running...");
if (Math.random() < 0.3) {
throw new RuntimeException("たまに失敗する"); // ← ここで例外
}
}, 0, 1, TimeUnit.SECONDS);
// ある瞬間に例外が飛ぶと、それ以降何も出力されなくなる
正しい書き方 ── タスク内で例外を握りつぶす
scheduler.scheduleAtFixedRate(() -> {
try {
// 本来の処理
doSomething();
} catch (Throwable t) {
// ログに出すなどして例外を握りつぶし、定期実行を継続させる
t.printStackTrace();
}
}, 0, 1, TimeUnit.SECONDS);
試験のひっかけパターン
- 「このコードは無限に実行され続ける」→ 例外が飛べば止まる
- 「例外がキャッチされないと
mainメソッドに伝播する」→ 別スレッドなので伝播しない - 「例外の内容はどこで確認できるか」→ 戻り値の
ScheduledFutureに対してget()を呼ぶとExecutionExceptionでラップされて取れる
5. shutdown ── 呼ばないとJVMが終了しない
これも頻出の落とし穴です。
public static void main(String[] args) {
ScheduledExecutorService s = Executors.newScheduledThreadPool(1);
s.schedule(() -> System.out.println("Hello"), 1, TimeUnit.SECONDS);
// shutdown() を呼ばないと、main が終わってもJVMが終わらない
}
理由: スレッドプール内のスレッドは 非デーモンスレッド として生成される。非デーモンスレッドが1つでも生きていると、JVMは終了しない。
shutdown / shutdownNow / awaitTermination
| メソッド | 挙動 |
|---|---|
shutdown() |
新規タスクの受付を停止。実行中・待機中のタスクは完了まで待つ。ブロックしない |
shutdownNow() |
新規受付停止 + 待機中タスクを返す + 実行中タスクに割り込み(interrupt) |
awaitTermination(timeout, unit) |
shutdownが完了するまで最大timeoutだけ待機。boolean を返す |
isShutdown() |
shutdown() が呼ばれたか |
isTerminated() |
すべてのタスクが完了したか |
試験で出るshutdownのパターン
ScheduledExecutorService s = Executors.newScheduledThreadPool(2);
// 定期タスクを登録
s.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
s.shutdown(); // ← この時点ではまだ定期タスクが動き続ける?
boolean done = s.awaitTermination(5, TimeUnit.SECONDS);
if (!done) {
s.shutdownNow(); // タイムアウトしたら強制停止
}
よくある誤解
-
shutdown()を呼んでもスケジュール済み・実行待ちのタスクは実行される - 「すぐに止まる」は誤り。止めたいなら
shutdownNow() - ただし
shutdownNow()でも、割り込みに反応しないタスクは止まらない
6. ScheduledFuture ── 戻り値を活用する
schedule 系メソッドの戻り値 ScheduledFuture<?> を使うと、タスクのキャンセルや結果取得ができます。
ScheduledFuture<?> handle = scheduler.scheduleAtFixedRate(
() -> System.out.println("beep"), 0, 1, TimeUnit.SECONDS);
// 10秒後にキャンセル
scheduler.schedule(
() -> handle.cancel(false), // false: 実行中でも割り込まない
10, TimeUnit.SECONDS);
Future と ScheduledFuture の継承関係
Future<V> ← get, cancel, isDone, isCancelled
└─ ScheduledFuture<V> ← getDelay も使える
└─ RunnableScheduledFuture<V> ← run メソッドを持つ
覚えておくべきメソッド
-
cancel(boolean mayInterruptIfRunning)── キャンセル。引数で「実行中でも割り込むか」を指定 -
get()── 結果を待機して取得(Callable版)。例外はExecutionExceptionでラップされる -
isDone()── 完了(正常・例外・キャンセルいずれも含む) -
getDelay(TimeUnit unit)── 次の実行までの残り時間
7. Runnable vs Callable ── scheduleで渡せるもの
schedule() にはRunnableもCallableも渡せますが、違いを押さえておきます。
// Runnable版 ── 戻り値なし
ScheduledFuture<?> f1 = scheduler.schedule(
() -> System.out.println("run"),
1, TimeUnit.SECONDS);
f1.get(); // → 常に null
// Callable版 ── 戻り値あり
ScheduledFuture<Integer> f2 = scheduler.schedule(
() -> 42, // Callable<Integer>
1, TimeUnit.SECONDS);
Integer result = f2.get(); // → 42
scheduleAtFixedRate / scheduleWithFixedDelay はRunnableのみ
定期実行系のメソッドはCallableを受け付けない(毎回の戻り値を返す方法がないため)。試験でたまに「Callableを渡せるのはどれか」と問われる。
8. 関連クラスとの関係 ── 並列処理章の全体像
試験では ScheduledExecutorService 単体ではなく、並列処理の他のクラスと絡めて問われることがあります。
| 分類 | クラス/インタフェース | 用途 |
|---|---|---|
| タスク定義 |
Runnable / Callable<V>
|
実行するタスク |
| 基本実行 | ExecutorService |
スレッドプールでタスク実行 |
| スケジュール | ScheduledExecutorService |
遅延・定期実行 |
| 結果受取 |
Future<V> / ScheduledFuture<V>
|
非同期結果の取得・キャンセル |
| 同期 | CountDownLatch |
指定回数のカウントダウンを待つ |
| 同期 | CyclicBarrier |
全スレッドが集合するまで待つ |
| コレクション | ConcurrentHashMap |
スレッドセーフなMap |
| コレクション | CopyOnWriteArrayList |
読み多・書き少向けList |
参考リンク
試験公式
Java公式ドキュメント
- ScheduledExecutorService (Java SE 11 日本語)
- ExecutorService (Java SE 21)
- Executors (Java SE 21)
- ScheduledFuture (Java SE 21)
- java.util.concurrent パッケージ概要
推奨書籍
- 徹底攻略 Java SE 11 Gold 問題集(通称: 黒本)
- オラクル認定資格教科書 Javaプログラマ Gold SE11(通称: 紫本)