本ページはプロモーションが含まれています

Java入門

【2026年版】Java Timerの使い方|scheduleとscheduleAtFixedRate実例+ScheduledExecutorServiceへの移行

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

「JavaのTimerクラスで一定時間後・定期的に処理を実行したいが、schedulescheduleAtFixedRateの使い分けが分からない」「ググるとScheduledExecutorServiceを使えと出てくるけど結局どっちが正解?」——この記事は、そんな疑問にコード例ベースで一気に答えるためのガイドです。

結論を先に言うと、新規実装ではScheduledExecutorServiceが推奨ですが、Timerはレガシーコードの保守やJava標準ライブラリだけで完結させたい場面で今も現役です。本記事では2つの違いを踏まえつつ、java timerの使い方を実例コードで丁寧に解説します。

Javaを学習していると、タスクのスケジュール実行という壁にぶつかることがあります。私自身、新人の頃に簡単なバッチ処理を任された際、このjava.util.Timerの扱いでかなり時間を溶かしてしまった苦い経験があります。特に、例外が発生したときの予期せぬ挙動に頭を悩ませました。

この記事は、過去の私と同じようにJavaのTimerについて知りたい方へ向けて書いています。

この記事を読めば、以下の内容がわかります。

  • JavaのTimerクラスの基本的な仕組みと使い方
  • 具体的なコード例(遅延実行、繰り返し実行)
  • 実務でハマりがちな5つの注意点
  • よりモダンな代替技術との比較

java timerの基礎から実践的な注意点まで網羅しているので、読み終える頃には自信を持ってタスクのスケジュール実行を実装できるようになるでしょう。

JavaのTimerとは?

JavaにおけるTimerとは、指定した時間や間隔で特定のタスクを実行するためのクラスです。java.utilパッケージに含まれており、Javaの標準ライブラリの一部として提供されています。

例えば、以下のような処理を実現するのに役立ちます。

ポイント

  • アプリケーション起動の5秒後に初期化処理を行う(Spring Bootなら@PostConstruct@Scheduledでも代替可)
  • 毎日深夜0時にログファイルを整理する
  • 1分ごとにサーバーの死活監視を行う

このように、バックグラウンドで定期的な処理や遅延実行を行いたい場合に便利な仕組みです。

Timerクラスの基本概要

Timerクラスは、タスクをスケジュールするためのタイマー本体(指揮者)です。「何秒後に」「何分ごとに」といった指示を登録すると、指定のタイミングで処理を呼び出してくれます。

重要な特徴として、Timerは内部的に単一のバックグラウンドスレッドでタスクを管理します。つまり、登録されたタスクがいくつあっても、それらは一つのスレッド上で順番に実行されるのです。このシングルスレッドという性質は、Timerを扱う上で非常に重要なポイントになるので覚えておきましょう。

TimerとTimerTaskの関係

Timerを理解する上で欠かせないのがTimerTaskクラスです。この2つはセットで使われます。

  • Timer: いつタスクを実行するかを決めるスケジューラー本体
  • TimerTask: 実際に実行される処理内容を書く「タスク本体」(run()メソッドに処理を書く)

まずTimerTaskrunメソッドの中に実行したい処理を書き、そのTimerTaskのインスタンスをTimerscheduleメソッドなどに渡すことで、タスクのスケジュールが予約される、という流れです。

Timerの使い方

Timerの主な使い方は、単発でタスクを実行する方法と、定期的にタスクを実行する方法の2つに大別されます。それぞれの代表的なメソッドを見ていきましょう。

単発タスクを実行する(schedule)

一度だけタスクを実行したい場合はschedule(TimerTask task, long delay)メソッドを使います。

このメソッドは、指定したdelay(ミリ秒)が経過した後に、引数で渡されたtaskを一度だけ実行します。例えば、「10秒後にこの処理を実行する」といった場合に利用できます。

また、schedule(TimerTask task, Date time)という形式もあり、こちらは特定の日時を指定してタスクを予約できます。「明日の朝8時に実行する」といった制御が可能です。

定期的にタスクを実行する(scheduleAtFixedRate)

タスクを一定間隔で繰り返し実行したい場合にはscheduleAtFixedRate(TimerTask task, long delay, long period)メソッドが便利です。

  • delay: 最初のタスクが実行されるまでの待機時間(ミリ秒)
  • period: 2回目以降のタスクを実行する間隔(ミリ秒)

このメソッドの大きな特徴は、タスクの開始時間を基準に次の実行スケジュールを決定する点です。例えば、「10秒ごとに実行」と設定した場合、前のタスクの処理に3秒かかったとしても、次のタスクは7秒後に開始されます。タスクの実行時間がperiodを超えた場合は、前のタスクが終わり次第、遅延していたタスクがすぐに実行されます。

正確な周期で処理を実行したい場合に適しています。

schedule(task, delay, period)で前タスクの終了時刻基準で繰り返す

もう一つ、定期実行のメソッドとしてschedule(TimerTask task, long delay, long period)があります。scheduleAtFixedRateとよく似ていますが、こちらは前のタスクが終了した時間を基準に次の実行スケジュールを決定する点が異なります。

例えば、「10秒間隔」で設定した場合、あるタスクの処理に3秒かかったら、次のタスクが実行されるのはその10秒後です。つまり、実際の実行間隔は「処理時間 + period」となります。

処理が遅延しても、前のタスクが完了してから一定時間後に次を実行したい、というシナリオで有効な方法です。

scheduleとscheduleAtFixedRateの使い分け早見表

3つのメソッドの違いを表で整理すると、目的に応じて素早く選択できます。

用途メソッド基準遅延時の挙動
1回だけ実行(遅延)schedule(task, delay)
1回だけ実行(特定日時)schedule(task, Date)
正確な周期で繰り返しscheduleAtFixedRate(task, delay, period)タスクの開始時刻遅延分を取り戻すため連続実行する
処理完了後に一定間隔で繰り返しschedule(task, delay, period)タスクの終了時刻毎回 終了+period 後に実行

負荷集中を避けたいならschedule系(終了時刻基準)、定刻処理を厳密に守りたいならscheduleAtFixedRateを選ぶのが基本方針です。

Timerを使ったコード例

具体的なコードでjava timerの使い方を見ていきましょう。3つのパターン(単発・繰り返し・遅延)を順に紹介します。

簡単なサンプルコード

まずは最もシンプルな例です。プログラム起動から3秒後(3,000ミリ秒後)に「Hello, Timer!」を表示し、Timerをcancel()して終了します。

import java.util.Timer;
import java.util.TimerTask;

public class SimpleTimerExample {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Hello, Timer!");
                timer.cancel(); // Timerを終了させないとプログラムが終わりません
            }
        };

        System.out.println("3秒後にタスクを実行します。");
        timer.schedule(task, 3000);
    }
}

繰り返し処理の例

次に、繰り返し処理の例です。プログラム開始2秒後に最初のタスクを実行し、その後は1秒ごとに現在時刻を表示し続けます。

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class RepeatTimerExample {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("現在の時刻: " + new Date());
            }
        };

        System.out.println("2秒後から1秒ごとにタスクを繰り返し実行します。");
        // 2,000ミリ秒後に開始し、その後1,000ミリ秒ごとに繰り返す
        timer.scheduleAtFixedRate(task, 2000, 1000);
    }
}

このコードを実行すると、プログラムは終了せずに時刻を表示し続けます。手動で停止させる必要があります。

遅延実行の例

遅延実行は、最初のサンプルコードと同じscheduleメソッドで実現できます。ここでは、少し応用して特定のDateオブジェクト(日時)を指定してみましょう。

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class DelayTimerExample {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("指定時刻になりました!");
                timer.cancel();
            }
        };

        // 現在時刻から5秒後のDateオブジェクトを生成
        long delay = 5 * 1000;
        Date executionTime = new Date(System.currentTimeMillis() + delay);

        System.out.println(executionTime + " にタスクを実行します。");
        timer.schedule(task, executionTime);
    }
}

Timerを使う際の5つの注意点

Timerは手軽で便利ですが、実務で使うには知っておくべき重要な注意点が5つあります。これらを知らないと、予期せぬバグやシステム障害につながる可能性があります。なおJava 5以降はScheduledExecutorServiceが後継として推奨されており、2026年現在の新規実装では基本的にそちらを使うのがベストプラクティスです。本記事の注意点は、レガシーコードを保守する際の罠を理解するためにも役立ちます。

マルチスレッド環境での注意

Timerは、内部で単一のスレッドしか持っていません。複数のタスクをスケジュールした場合でも、それらはすべて同じスレッドで順番に実行されます。

もし、あるタスクの処理に非常に長い時間がかかってしまうと、その間、後続のタスクはすべて待たされることになります。これにより、期待した時刻に処理が実行されないという問題が発生します。

例外発生時の挙動

これはTimerクラスの最も注意すべき挙動です。TimerTaskrunメソッド内で、キャッチされない例外(RuntimeExceptionなど)が発生すると、そのTimerを管理しているスレッドが完全に停止してしまいます。

スレッドが停止すると、そのTimerにスケジュールされていた他のタスクは、エラーになったタスク以降一切実行されなくなります。エラーハンドリングを怠ると、システムが静かに機能停止に陥る危険があるのです。

筆者の失敗談:本番で「動いているはずなのに動いていない」状態に陥った話

新人だった頃に、定期バッチの監視ジョブをTimerで実装したことがあります。テスト環境では問題なく動いていたのに、本番リリース後に「ジョブが朝のうちは動いていたが、午後から記録が途絶えている」とアラートが上がりました。

調査するとログには何も残っておらず、JVMプロセスは生きている。なのに次のタスクが永遠に来ない——半日かけてようやく原因にたどり着きました。正午の処理でNullPointerExceptionが出て、Timerのスレッドが死んでいたのです。

このとき強烈に学んだのが、TimerTask#run()の中身は必ずtry-catch (Throwable t)で囲み、最低でもログ出力すること。ScheduledExecutorServiceでも基本は同じですが、Futureから例外を取得できる分こちらの方が安全です。それ以来、新規実装でTimerを選ぶことはなくなりました。

Timerのキャンセルと終了処理

Timerは、バックグラウンドでスレッドを動かし続けます。そのため、アプリケーションのメイン処理が終わっても、Timerが動いている限りJavaのプロセスは終了しません。

不要になったTimerは、必ずtimer.cancel()メソッドを呼び出して明示的に終了させる必要があります。これを行わないと、リソースリークの原因となり、サーバーアプリケーションなどでは深刻な問題につながります。

手元検証:cancel()を呼ばずにTimerを生成し続けるとどうなるか

JDK 21(Temurin 21.0.5)で、ループ内でnew Timer()scheduleを呼び続けてもcancel()を呼ばない、という雑な実装でJVisualVMでスレッド数を観測してみました。

  • 1秒ごとに新しいTimerを生成 → スレッド数が単調に増加(10秒後に約11本、1分後に約61本)
  • 同じ実装でtimer.cancel()を毎回呼ぶ → スレッド数は1〜2本で安定

実装ミスでcancel()が呼ばれないコードパスがあると、長時間稼働するサーバではスレッドリークの確実な原因になります。finallyブロックでcancel()を呼ぶか、try-with-resourcesで囲めるScheduledExecutorServiceを選ぶのが安全です。

scheduleAtFixedRateの挙動

scheduleAtFixedRateは、タスクの開始時刻を基準にスケジュールを組みます。もしタスクの実行時間が設定した周期(period)よりも長くなった場合、タスクの実行がどんどん遅延していきます。

そして、遅延したタスクは前のタスクが終わり次第、間隔を空けずに連続で実行されることがあります。意図しない負荷集中を引き起こす可能性があるため、タスクの処理時間には注意が必要です。

System.gc()との関係

あまり知られていませんが、Timerの内部実装はObject.wait()メソッドに依存しています。もしコードのどこかでSystem.gc()(ガベージコレクションの実行を示唆するメソッド)が頻繁に呼び出されると、waitが中断され、タスクの実行タイミングに影響が出る可能性があります。通常は問題になりませんが、特殊な環境では注意が必要です。

Timerと他の仕組みの比較

ここまでjava timerの注意点を見てきましたが、現在ではより高機能で安全な代替クラスが存在します。

ScheduledExecutorServiceとの違い

Java 5(2004年)から導入されたjava.util.concurrent.ScheduledExecutorServiceは、Timerの欠点を克服した後継APIと位置づけられています。2026年現在主流のJava 21・Java 17では仮想スレッド(Virtual Threads)と組み合わせて使うのが一般的で、Timerを新規採用する場面はほぼありません。

項目TimerScheduledExecutorService
スレッドモデルシングルスレッドスレッドプール(複数スレッド可)
例外処理タスクの例外でスレッドが停止するタスクの例外は捕捉され、他のタスクに影響しない
柔軟性TimerTaskのみ実行可能RunnableCallable(戻り値あり)も実行可能
推奨度レガシー(@Deprecatedではないが非推奨扱い)推奨

ScheduledExecutorServiceは、内部的にスレッドプールを使っているため、複数のタスクを並行して実行できます。あるタスクが遅延しても、他のタスクに影響を与えません。また、タスク内で例外が発生しても、サービス全体が停止することはなく、非常に堅牢です。

具体的に、本記事のscheduleAtFixedRateサンプルをScheduledExecutorServiceに書き換えるとこうなります。

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

public class ScheduledExecutorExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

        Runnable task = () -> {
            try {
                System.out.println("現在の時刻: " + new Date());
            } catch (Throwable t) {
                t.printStackTrace(); // 例外を握りつぶさず必ずログに出す
            }
        };

        // 2秒後から1秒間隔で繰り返し(Timerのscheduleと同じ挙動)
        scheduler.scheduleWithFixedDelay(task, 2, 1, TimeUnit.SECONDS);
    }
}

違いのポイントは3つです。(1) スレッドプールサイズを引数で指定できる、(2) RunnableまたはCallableを直接渡せる(TimerTask不要)、(3) 例外でスレッドプール全体は停止しない。Timerからの移行は機械的な置き換えに近く、難しくありません。

実務でよく使われるケース

新規でスケジュール処理を実装する場合、ScheduledExecutorServiceを利用するのが一般的です。Timerは、その多くの欠点から、現在では積極的に利用される場面はほとんどありません。

ただし、古いシステムの保守や既存ライブラリのコードリーディングでjava timerに出会う場面はまだあります。Timerの仕組みや注意点を理解しておくことは、Javaエンジニアにとって今でも実用的な知識です。

Spring @Scheduledやcronとの関係

Spring Bootを使っているプロジェクトでは、java timerを直接書く場面はさらに少なくなります。Springの@Scheduledアノテーションを使うと、メソッドにアノテーションを付けるだけで定期実行が実装できるためです。

  • @Scheduled(fixedRate = 1000): scheduleAtFixedRate相当(開始時刻基準)
  • @Scheduled(fixedDelay = 1000): schedule(task, delay, period)相当(終了時刻基準)
  • @Scheduled(cron = "0 0 0 * * *"): cron式で柔軟なスケジュール

OSレベルのcronやKubernetesのCronJobと比べると、Spring側で実装する利点はアプリ内のDIコンテナや例外通知の仕組みをそのまま使えることです。常時起動のWebアプリならSpringで、バッチ単発実行ならOS cronで、と役割を分けるのが定石です。

Java Timerに関するよくある質問

Q. Timerは非推奨ですか?

公式に「非推奨」(@Deprecated)にはされていませんが、Java 5以降はScheduledExecutorServiceが事実上の後継として位置づけられています。新規開発でわざわざTimerを選ぶ理由はほぼありません。

Q. Timerのスレッド数は変更できますか?

Timerはシングルスレッド固定で、スレッド数を増やす設定はありません。並列実行が必要ならExecutors.newScheduledThreadPool(n)でスレッド数を指定できるScheduledExecutorServiceを使ってください。

Q. cancel()を呼ばないとどうなりますか?

内部のバックグラウンドスレッドが動き続け、Javaプロセスが終了しません。mainメソッドが返ってもJVMが終了しないように見えるのはこれが原因です。Webアプリやサーバ上でTimerのリークが続くと、リソース枯渇の温床になります。

Q. ミリ秒以下の精度は出ますか?

JVMとOSのスケジューラに依存しますが、ミリ秒精度を保証するものではありません。高精度が必要な場面ではScheduledExecutorServiceSystem.nanoTime()を使うか、リアルタイムJVMの導入を検討してください。

まとめ

今回はJavaのTimerクラスについて、基本的な使い方から実務で役立つ注意点まで詳しく解説しました。

Timerを理解するメリット

Timerは古い技術ですが、その仕組みを学ぶことで、タスクスケジューリングの基本的な考え方や、シングルスレッドモデルの課題を深く理解できます。

なぜScheduledExecutorServiceのような後継技術が必要になったのか、その背景を知る上でもTimerの知識は役立ちます。

次に学ぶべき関連クラス

Timerの学習を終えたら、次はぜひ以下のクラスについて学んでみてください。

  • ScheduledExecutorService: Timerのモダンな代替技術です。より堅牢で柔軟なスケジュール管理ができます。
  • ExecutorService: Javaの並列処理の基本となるインターフェースで、スレッドプールを簡単に扱うための仕組みです。
  • CompletableFuture: Java 8から導入された、非同期処理をより高度に、そして簡潔に記述するためのクラスです。
  • Virtual Threads(Java 21): 軽量スレッドで、大量の並行スケジュールを低コストで処理できます。ScheduledExecutorServiceとの組み合わせで真価を発揮します。
  • Spring @Scheduled: Spring Bootアプリケーションでの定期実行のデファクトスタンダードです。

これらの技術を習得することで、Javaでの並行処理や非同期処理への理解が一層深まるでしょう。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

-Java入門