HOWTO · Java

Java で数秒遅らせる方法

ブロックする一時停止に Thread.sleep() を使うべき場合と、Java で遅延タスクや繰り返しタスクをスケジュールすべき場合を学びます。

このページの内容

単純に処理を一時停止する場合は、Thread.sleep() を呼び出します。指定した時間だけ、現在のスレッドが停止します。呼び出し元のスレッドを動作させたまま、後で処理を実行したり、繰り返し実行したりする場合は、ScheduledExecutorService を使います。以下の例では、2つの方法と割り込みへの対処、スケジュールした処理の後片付けを説明します。
実行の一時停止は、Java チュートリアル のユーティリティグループに属し、日付、乱数、コンソールの日常レシピと並んでいます。

単純にスレッドを停止するには Thread.sleep() を使う

Thread.sleep() はミリ秒単位の時間を受け取り、呼び出したスレッドを一時停止します。次のスタンドアロンプログラムは、1秒待ってからメッセージを出力します。main メソッドは InterruptedException を宣言しているため、割り込みを握りつぶさず、呼び出し元に伝えます。

public class Main {
  public static void main(String[] args) throws InterruptedException {
    Thread.sleep(1000);
    System.out.println("One second has passed.");
  }
}

呼び出し元に InterruptedException を渡せないメソッドでは、例外を捕捉し、Thread.currentThread().interrupt() で割り込み状態を復元します。そうすれば、上位のコードがキャンセル要求を検出し、アプリケーションの方針に沿って対応できます。スリープするのは現在のスレッドだけであり、プロセス内のほかのスレッドは停止しません。

遅延の単位を明示するには TimeUnit.sleep() を使う

TimeUnit.SECONDS.sleep(2) と書くと、2秒間の停止を直接表せます。この方法も呼び出し元のスレッドをブロックし、割り込みを受けることがあるため、Thread.sleep() と同じ方針で割り込みに対処してください。

import java.util.concurrent.TimeUnit;

public class Main {
  public static void main(String[] args) {
    try {
      TimeUnit.SECONDS.sleep(2);
      System.out.println("Two seconds have passed.");
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      System.out.println("Pause interrupted.");
    }
  }
}

時間の単位を明示すると期間を理解しやすくなる場合は、この書き方を選びます。TimeUnit.sleep() は時間指定の待機を読みやすく表現する方法です。遅延中も現在のスレッドをほかの処理に使えるようにする方法ではありません。

ScheduledExecutorService でタスクを繰り返し実行する

ScheduledExecutorService を使うと、呼び出し元のスレッドを動作させたまま、遅延後または一定間隔で Runnable を実行できます。scheduleAtFixedRate() には、タスク、初回実行までの遅延、実行間隔、TimeUnit を渡します。Executor がタスクの run() メソッドを呼び出します。処理の内容は run() に記述し、run() 自体が次の実行をスケジュールするわけではありません。

このプログラムは1秒後にタスクを開始し、その後も1秒ごとに実行するよう要求します。別のスケジュール済み処理が5秒後に繰り返しタスクをキャンセルし、Executor を終了します。実行時の状況によってスケジュールの進み方は変わるため、出力行の数やタイミングは一定ではありません。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class Main {
  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    ScheduledFuture<?> task = executor.scheduleAtFixedRate(
        () -> System.out.println("Task running"), 1, 1, TimeUnit.SECONDS);
    executor.schedule(() -> {
      task.cancel(false);
      executor.shutdown();
    }, 5, TimeUnit.SECONDS);
  }
}

繰り返しタスクをキャンセルする可能性がある場合は、戻り値の ScheduledFuture を保持してください。アプリケーションで不要になった Executor は終了します。終了しないと、ワーカースレッドによってプロセスが終了しないことがあります。遅延後に1回だけ実行する場合は、繰り返し実行の代わりに schedule() を使います。

ScheduledExecutorService でメソッド参照を使う

既存の引数なしメソッドをタスクとして使う場合は、メソッド参照を Runnable として渡せます。このプログラムは2秒後にメソッドを1回呼び出すようスケジュールし、その後 Executor を終了します。終了後は新しいタスクを送信できなくなりますが、すでにキューに入っているタスクは完了できます。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main {
  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    executor.schedule(Main::executeTask, 2, TimeUnit.SECONDS);
    executor.shutdown();
  }

  private static void executeTask() {
    System.out.println("Task running");
  }
}

現在のスレッドを停止してよい短い待機には、sleep メソッドを使います。ほかの処理を続けたい場合や、タスクを後で、または繰り返し実行したい場合は、スケジュール実行を選びます。sleep は呼び出し元のスレッドをブロックしますが、Executor の後片付けは不要です。スケジュールした処理ではそのスレッドを使い続けられる一方、不要になった Executor は終了し、繰り返しタスクはキャンセルする必要があります。