HOWTO · Kotlin

Kotlin の Sleep でスレッドの実行を一時停止する

Thread.sleep や TimeUnit で Kotlin/JVM のスレッドを一時停止する方法と、delay や ScheduledExecutorService を使う場面を説明します。

このページの内容

同期的な Kotlin/JVM コードで現在のスレッドを一時停止する必要がある場合は Thread.sleep を使います。名前付きの時間単位で遅延を明確にしたい場合は TimeUnit.sleep を使います。コルーチンではスレッドをブロックせずにコルーチンを停止する delay を使い、後でタスクを実行する場合は ScheduledExecutorService を使います。スリープ中のスレッドは中断される可能性があり、遅延は最小待機時間であって正確な実行時刻ではありません。

coroutine は非ブロッキングな実行単位を表す技術用語です。

Thread.sleep で現在のスレッドをブロックする

Thread.sleep(milliseconds) は、呼び出したスレッドを一時停止する JVM の直接的な操作です。引数はミリ秒を表す Long で、メソッドは InterruptedException をスローします。本番コードではスタックトレースを表示するだけでなく、中断フラグを復元するか中断を呼び出し元へ渡します。

fun main() {
    println("before")
    try {
        Thread.sleep(100L)
        println("after")
    } catch (exception: InterruptedException) {
        Thread.currentThread().interrupt()
        println("interrupted")
    }
}

出力:

before
after

Thread から Kotlin の Sleep 関数を使う

呼び出し元のスレッドは少なくとも約 100 ミリ秒ブロックされます。JVM 全体が停止するわけではなく、実行可能な他のスレッドは続行できます。イベントループや限られたワーカースレッドでは、非ブロッキングな停止として表現できる処理にこの方法を使わないでください。

TimeUnit.sleep で読みやすい単位を使う

java.util.concurrent の TimeUnit は、スリープ前に秒やミリ秒などの名前付き単位を変換します。現在のスレッドをブロックし、中断にも応答しますが、TimeUnit.SECONDS.sleep(1) はミリ秒へのリテラル変換より確認しやすい表現です。

import java.util.concurrent.TimeUnit

fun main() {
    println("before")
    try {
        TimeUnit.MILLISECONDS.sleep(100)
        println("after")
    } catch (exception: InterruptedException) {
        Thread.currentThread().interrupt()
        println("interrupted")
    }
}

出力:

before
after

TimeUnit から Kotlin の Sleep 関数を使う

要件に合う単位を選びます。スケジューリング、競合、または OS のタイマー精度によってスレッドの再開が遅れるため、変換は正確なタイミングを保証しません。

コルーチン内で delay を使う

delay は kotlinx-coroutines-core のサスペンド関数です。基盤のスレッドをブロックせずにコルーチンを停止するため、そのスレッドを別のコルーチンが利用できます。サスペンド関数またはコルーチンスコープから呼び出す必要があり、通常の同期コードで Thread.sleep の代わりにはなりません。

Kotlin の delay 関数を使う

たとえば runBlocking スコープでコルーチンを起動し、delay(100L) の後にメッセージを表示できます。表示順はスケジューリングに依存します。また delay はキャンセル可能なので、キャンセルされたコルーチンは通常サスペンド地点で停止します。コルーチンコードをコンパイルする前に kotlinx-coroutines-core を追加してください。Kotlin 標準ライブラリだけでは delay を提供しません。

ScheduledExecutorService で処理をスケジュールする

要件が「現在のスレッドをスリープさせる」ではなく「このタスクを後で実行する」である場合は ScheduledExecutorService を使います。スケジュール済みタスクは実行前にキャンセルでき、不要になった Executor は終了させます。

import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

fun main() {
    val executor = Executors.newSingleThreadScheduledExecutor()
    val task = executor.schedule(
        { println("task ran") },
        100,
        TimeUnit.MILLISECONDS,
    )

    task.get()
    executor.shutdown()
}

出力:

task ran

ここでの get() は、タスクが完了するまで短いコマンドライン例を実行し続けるためだけに使っています。アプリケーションでキャンセルが必要な場合は返された ScheduledFuture を保持し、ライフサイクル方針に従って shutdown または shutdownNow を使います。スケジューリングは正確なリアルタイム保証ではありません。

どの Kotlin Sleep 方法を選ぶべきか

  • 現在のスレッドを意図的にブロックする小さな同期 JVM 処理には Thread.sleep を使います。
  • 同じブロッキング動作を読みやすい単位で書くには TimeUnit.sleep を使います。
  • コルーチン内のキャンセル可能で非ブロッキングな停止には delay を使います。
  • 遅延後に別のタスクを起動するには ScheduledExecutorService を使います。

一般的な Sleep 操作として wait() を使わないでください。Java の Object.wait() はモニター連携の一部で、適切なモニターを必要とする別の同期問題のためのものです。どの API を選んでも、中断とキャンセルは予期しないエラーではなく通常の制御フローとして扱ってください。