「JavaのTimerクラスで一定時間後・定期的に処理を実行したいが、scheduleとscheduleAtFixedRateの使い分けが分からない」「ググると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の標準ライブラリの一部として提供されています。
例えば、以下のような処理を実現するのに役立ちます。
バックグラウンドで定期的な処理や遅延実行を行いたい場合に便利な仕組みです。
Timerクラスの基本概要
Timerクラスは、タスクをスケジュールするためのタイマー本体(指揮者)です。
「何秒後に」「何分ごとに」といった指示を登録すると、指定のタイミングで処理を呼び出してくれます。
重要な特徴として、Timerは内部的に単一のバックグラウンドスレッドでタスクを管理します。
つまり、登録されたタスクがいくつあっても、それらは一つのスレッド上で順番に実行されるのです。
このシングルスレッドという性質は、Timerを扱う上で非常に重要なポイントになるので覚えておきましょう。
TimerとTimerTaskの関係
Timerを理解する上で欠かせないのがTimerTaskクラスです。この2つはセットで使われます。
Timer: いつタスクを実行するかを決めるスケジューラー本体TimerTask: 実際に実行される処理内容を書く「タスク本体」(run()メソッドに処理を書く)
まずTimerTaskのrunメソッドの中に実行したい処理を書きます。
そのTimerTaskのインスタンスをTimerのscheduleメソッドなどに渡すことで、タスクのスケジュールが予約されます。
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クラスの中で最も注意すべきポイントです。
TimerTaskのrunメソッド内で、キャッチされない例外(RuntimeExceptionなど)が発生すると、そのTimerを管理しているスレッドが完全に停止してしまいます。
スレッドが停止すると、そのTimerにスケジュールされていた他のタスクは、エラーになったタスク以降一切実行されなくなります。
エラーハンドリングを怠ると、システムが静かに機能停止に陥る危険があるのです。
Timerのキャンセルと終了処理
Timerは、バックグラウンドでスレッドを動かし続けます。
そのため、アプリケーションのメイン処理が終わっても、Timerが動いている限りJavaのプロセスは終了しません。
不要になったTimerは、必ずtimer.cancel()メソッドを呼び出して明示的に終了させる必要があります。
cancel()を呼ばないと、リソースリークの原因となり、サーバーアプリケーションなどでは深刻な問題につながります。
scheduleAtFixedRateはどんな挙動をしますか?
scheduleAtFixedRateは、タスクの開始時刻を基準にスケジュールを組みます。もしタスクの実行時間が設定した周期(period)よりも長くなった場合、タスクの実行がどんどん遅延していきます。
そして、遅延したタスクは前のタスクが終わり次第、間隔を空けずに連続で実行されることがあります。
意図しない負荷集中を引き起こす可能性があるため、タスクの処理時間には注意が必要です。
System.gc()を呼ぶとTimerの動作に影響しますか?
あまり知られていませんが、Timerの内部実装はObject.wait()メソッドに依存しています。Oracle公式のTimerクラスのドキュメントも「ガベージコレクションなどのバックグラウンド処理によって実行が遅延することがある」と明記しており、GCの一時停止(Stop-The-World)でタイマースレッドの起床が遅れ、タスクの実行タイミングがずれる可能性があります。通常は問題になりませんが、特殊な環境では注意が必要です。
Timerと他の仕組みの比較

ここまでjava timerの注意点を見てきましたが、現在ではより高機能で安全な代替クラスが存在します。
ScheduledExecutorServiceとの違い
ScheduledExecutorServiceとは、スレッドプールを使ってタスクを定期実行できる、java.util.concurrentパッケージのインターフェースです。
Java 5(2004年)から導入され、Timerの欠点を克服した後継APIと位置づけられています。
2026年8月時点の最新LTSはJava 25(2025年9月リリース、8年間サポート)で、Java 21で正式導入された仮想スレッド(Virtual Threads)と組み合わせて使うのが一般的です。
なおJava 17は仮想スレッドを含まないため、その恩恵を受けるにはJava 21以降への移行が前提になります。
いずれにせよ、Timerを新規採用する場面はほぼありません。
| 項目 | Timer | ScheduledExecutorService |
| スレッドモデル | シングルスレッド | スレッドプール(複数スレッド可) |
| 例外処理 | タスクの例外でスレッドが停止する | タスクの例外は捕捉され、他のタスクに影響しない |
| 柔軟性 | TimerTaskのみ実行可能 | RunnableやCallable(戻り値あり)も実行可能 |
| 推奨度 | レガシー(@Deprecatedではないが非推奨扱い) | 推奨 |
ScheduledExecutorServiceは、内部的にスレッドプールを使っているため、複数のタスクを並行して実行できます。
あるタスクが遅延しても、他のタスクに影響を与えません。また、タスク内で例外が発生しても、サービス全体(スレッドプール)が停止することはありません。
ただし、例外を起こした定期タスクそのものは以降実行されなくなる点はTimerと同じで、この点は後述の「手元検証」で詳しく確認します。
具体的に、本記事の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からの移行は機械的な置き換えに近く、難しくありません。
scheduleWithFixedDelayとscheduleAtFixedRateの違い
上のコード例ではscheduleWithFixedDelayを使いましたが、ScheduledExecutorServiceにはscheduleAtFixedRateも存在します。
両者の違いは、TimerのscheduleAtFixedRateとschedule(task, delay, period)の関係とほぼ同じです。
scheduleAtFixedRate(command, initialDelay, period, unit): 前回タスクの開始時刻を基準に次回を計算する(TimerのscheduleAtFixedRate相当)scheduleWithFixedDelay(command, initialDelay, delay, unit): 前回タスクの終了時刻を基準に次回を計算する(Timerのschedule(task, delay, period)相当)
正確な周期を厳密に維持したい場合はscheduleAtFixedRate、処理の重なりを避け負荷を平準化したい場合はscheduleWithFixedDelayを選ぶのが基本方針です。
ScheduledExecutorServiceのシャットダウン処理を忘れずに
ScheduledExecutorServiceはTimerと違い、明示的にshutdown()(またはshutdownNow())を呼ばない限りスレッドプールが動き続けます。
Timerのcancel()に相当する終了処理を忘れると、アプリケーション終了時にJVMが正常終了しない、あるいはスレッドリークにつながる点は共通の落とし穴です。
shutdown(): 新規タスクの受付を停止し、実行中・待機中のタスクは完了させてから終了するshutdownNow(): 実行中のタスクを中断し、待機中のタスクも破棄して即座に終了する
アプリケーションのライフサイクルに合わせて、@PreDestroyやシャットダウンフックでshutdown()を呼び出す実装にしておくと安全です。
実務でよく使われるケース
新規でスケジュール処理を実装する場合、ScheduledExecutorServiceを利用するのが一般的です。
Timerは、その多くの欠点から、現在では積極的に利用される場面はほとんどありません。
ただし、古いシステムの保守や既存ライブラリのコードリーディングでjava timerに出会う場面はまだあります。
Timerの仕組みや注意点を理解しておくことは、Javaエンジニアにとって今でも実用的な知識です。
私の判断基準としては、「レガシーのTimer実装を見つけたら即座に置き換えるべきか」は一律には決めていません。
本番で稼働中かつ障害実績がないなら、無理に触らないのが基本方針です。
前述の「手元検証」で示したとおり、例外で1本のスレッドが死んでも他プロセスに波及しない設計であれば、リスクとリファクタリングコストが見合わないことが多いためです。
逆に、複数のタスクを1つのTimerに相乗りさせている実装(=1つの例外が他のタスクまで巻き添えにする構造)を見つけたときは話が別です。
優先度を上げてScheduledExecutorServiceへの置き換えを提案しています。
Timerからの移行チェックリスト
既存のTimer実装をScheduledExecutorServiceに置き換える際は、以下の4点を順に確認すると移行漏れを防げます。
- TimerTaskをRunnable/Callableに置き換える:
run()メソッドの中身をそのままRunnableのラムダに移す - scheduleとscheduleAtFixedRateの対応を確認する: 開始時刻基準なら
scheduleAtFixedRate、終了時刻基準ならscheduleWithFixedDelayを選ぶ - cancel()をshutdown()に置き換える: 終了処理を書き忘れるとTimer時代と同じくスレッドリークになる
- 例外処理をtry-catchからFutureベースに見直す:
submit()の戻り値Futureからget()で例外を取得できるようにしておくと、握りつぶしを防げる
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のスケジューラに依存しますが、ミリ秒精度を保証するものではありません。高精度が必要な場面ではScheduledExecutorService+System.nanoTime()を使うか、リアルタイムJVMの導入を検討してください。
Q. TimerとScheduledExecutorServiceは例外発生時に同じ挙動になりますか?
はい、「例外を起こした定期タスクが以降実行されなくなる」という結果自体は同じです。違うのは影響範囲で、Timerは同じスレッドに乗っていた他のタスクまで巻き添えで止まりますが、ScheduledExecutorServiceはそのタスクだけに影響が限定され、Future経由で例外を検知できます。詳しくは本記事の「手元検証」で実測結果を紹介しています。
Q. ScheduledExecutorServiceのshutdown()を呼び忘れるとどうなりますか?
Timerのcancel()忘れと同様、スレッドプールが動き続けてJavaプロセスが終了しません。長時間稼働するアプリケーションではスレッドリークの原因になるため、@PreDestroyやシャットダウンフックでshutdown()を呼ぶ実装にしておく必要があります。
Q. Spring BootならTimerより@Scheduledを使うべきですか?
はい、Spring Bootプロジェクトでは@Scheduledを使うのが基本です。DIコンテナや例外通知の仕組みをそのまま使え、fixedRate/fixedDelay/cronで用途に応じた指定もできます。Timerを直接書く必要があるのは、Springを使わない小規模なJavaプログラムやレガシーコードの保守に限られます。
まとめ
今回はJavaのTimerクラスについて、基本的な使い方から実務で役立つ注意点まで詳しく解説しました。
Timerを理解するメリット
Timerは古い技術ですが、その仕組みを学ぶことで、タスクスケジューリングの基本的な考え方や、シングルスレッドモデルの課題を深く理解できます。
なぜScheduledExecutorServiceのような後継技術が必要になったのか、その背景を知る上でもTimerの知識は役立ちます。
次に学ぶべき関連クラス
Timerの学習を終えたら、次はぜひ以下のクラスについて学んでみてください。
ScheduledExecutorService:Timerのモダンな代替技術です。より堅牢で柔軟なスケジュール管理ができます。ExecutorService: Javaの並列処理の基本となるインターフェースで、スレッドプールを簡単に扱うための仕組みです。CompletableFuture: Java 8から導入された、非同期処理をより高度に、そして簡潔に記述するためのクラスです。- Virtual Threads(Java 21で正式導入、2026年時点の最新LTSであるJava 25でも安定運用中): 軽量スレッドで、大量の並行スケジュールを低コストで処理できます。
ScheduledExecutorServiceとの組み合わせで真価を発揮します。 - Spring
@Scheduled: Spring Bootアプリケーションでの定期実行のデファクトスタンダードです。
これらの技術を習得することで、Javaでの並行処理や非同期処理への理解が一層深まるでしょう。



