KotlinのTimerについて

採用はこちら

Kotlinで「Timer」を使った時間制御処理を学ぶ際には、仕組みの正体・スレッドモデル・例外時の挙動を正しく理解することが非常に重要です。

表面的な使い方だけを覚えると、実務では思わぬ不具合につながる可能性があります。

本稿では、KotlinにおけるTimerの本質から、正しい使い分け、注意点、代替手段までを体系的に解説します。

目次

KotlinのTimerの正体

Kotlinには「Kotlin専用のTimerクラス」が存在するわけではありません。

JVM環境においてKotlinで利用されるTimerは、Java標準ライブラリの java.util.Timer および TimerTask です。

KotlinはJavaと完全互換であるため、JavaのTimerをそのまま利用できます。

Kotlin標準ライブラリによる拡張関数

Kotlinでは、kotlin.concurrent パッケージにより、Timerをラムダ式で簡潔に書ける拡張関数が提供されています。

単発実行の例

import java.util.Timer
import kotlin.concurrent.schedule

fun main() {
    Timer().schedule(3000) {
        println("3秒後に実行されました")
    }
}

この schedule は、内部的には TimerTask を生成して登録しています。

戻り値は TimerTask であり、後から cancel() することも可能です。

定期実行と「間隔」の正確な意味

Timerには 2種類の定期実行方式 があり、ここは特に誤解されやすいポイントです。

schedule(固定ディレイ方式)

Timer().schedule(delay = 0, period = 1000) {
    println("定期実行")
}
  • 次の実行は 前回の処理が終了してから period ミリ秒後
  • 処理が重くなると、実行タイミングはどんどん後ろにずれていく

scheduleAtFixedRate(固定レート方式)

Timer().scheduleAtFixedRate(delay = 0, period = 1000) {
    println("定期実行")
}
  • 次の実行は 前回の開始時刻から period ミリ秒後
  • 遅れが出ると、間隔を詰めて実行しようとする

※ ただしTimerは単一スレッドのため、実際には完全な巻き戻し実行はできません。

Timerのスレッドモデル

Timerはシングルスレッド

Timer1つのTimerインスタンスにつき、1本のバックグラウンドスレッド を持ちます。

  • 複数の TimerTask を登録しても同時実行されない
  • すべてのタスクは順番に処理される

重い処理による影響

Timer().schedule(0, 1000) {
    Thread.sleep(3000)
    println("実行")
}

この場合、1秒ごとに実行されることはなく、処理時間に引きずられて実行が遅延します。

例外処理の重大な落とし穴

Timerの最も危険な特性のひとつが、例外耐性の弱さです。

事実として重要な点

  • TimerTask 内で 未捕捉の例外(RuntimeExceptionなど) が発生すると
  • Timerのスレッド自体が終了
  • 以後、すべてのタスクが実行されなくなる
Timer().schedule(1000) {
    throw RuntimeException("例外発生")
}

この仕様はTimer全体に影響するため、実務では致命的になり得ます。

TimerおよびTimerTaskの停止方法

Timer全体を停止する

val timer = Timer()
timer.cancel()
  • 登録済みタスクはすべて破棄
  • このTimerは再利用不可

特定のタスクだけ停止する

val task = Timer().schedule(0, 1000) {
    println("実行")
}

task.cancel()

Kotlinの scheduleTimerTask を返すため、このような制御が可能です。

AndroidでTimerを使う際の注意点

UIスレッド操作は禁止

TimerTaskはバックグラウンドスレッドで実行されます。
Androidでは、UI操作を直接行うとクラッシュします。

// NG
textView.text = "更新"

正しい対応

runOnUiThread {
    textView.text = "更新"
}

ただし、AndroidではTimer自体がライフサイクル管理と相性が悪いため、基本的には推奨されません。

Timerが実務で敬遠される理由

Timerはシンプルですが、設計上の弱点があります。

問題点内容
スレッドシングルスレッド
例外1回の例外で全停止
精度処理遅延に弱い
AndroidLifecycleと非親和

現代的な代替手段

Kotlin Coroutines

import kotlinx.coroutines.*

launch {
    delay(3000)
    println("3秒後に実行")
}
  • 例外分離が可能
  • キャンセル制御が明確
  • Android / サーバー両対応

定期処理(Coroutine)

launch {
    while (isActive) {
        println("1秒ごと")
        delay(1000)
    }
}

ScheduledExecutorService(JVM標準)

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

val scheduler = Executors.newScheduledThreadPool(1)

scheduler.scheduleAtFixedRate({
    println("定期実行")
}, 0, 1, TimeUnit.SECONDS)

Timerはいつ使うべきか?

使用しても問題ないケース

  • 学習目的
  • 非常に軽量な一時処理
  • 短命なJVMツール

避けるべきケース

  • Androidアプリ
  • 長時間稼働するサーバー
  • 定期実行の信頼性が求められる処理

まとめ

  • KotlinのTimerは java.util.Timer が正体
  • ラムダ記法は kotlin.concurrent の拡張関数
  • Timerは 単一スレッド・例外に極端に弱い
  • 実務では Coroutine または Executor系 が推奨

以上、KotlinのTimerについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次