4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java Gold ScheduledExecutorServiceのまとめ

4
Last updated at Posted at 2024-11-08

はじめに

Java Gold (SE 11 / SE 17) 試験における 「並列処理」章 は、Stream APIに次いで出題比率が高い分野です。中でも ScheduledExecutorService は、scheduleAtFixedRatescheduleWithFixedDelay の挙動の違いや、例外発生時の振る舞いなど 細かい仕様を問う問題 が頻繁に出題されます。
:::


1. 位置付け ── Executor階層での立ち位置

まず ScheduledExecutorService がどこに属するインタフェースかを整理します。試験ではインタフェースの継承関係が問われることがあります。

Executor                    ← execute(Runnable) のみ
  └─ ExecutorService         ← submit, shutdown, invokeAll など
       └─ ScheduledExecutorService  ← schedule, scheduleAtFixedRate など

試験で覚えておくこと

  • ScheduledExecutorServiceExecutorService継承している
  • したがって submit() / shutdown() / invokeAll() などもすべて使える
  • Executorexecute(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スレッドでスケジュール実行

newScheduledThreadPoolnewSingleThreadScheduledExecutor だけがスケジュール機能を持つ。他のファクトリメソッドで返る 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);

FutureScheduledFuture の継承関係

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公式ドキュメント

推奨書籍

  • 徹底攻略 Java SE 11 Gold 問題集(通称: 黒本)
  • オラクル認定資格教科書 Javaプログラマ Gold SE11(通称: 紫本)
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?