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

Java入門

Java Timerの基本!3つの使い方と5つの注意点を解説

トム

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

「Javaで特定の時間に処理を実行させたいけど、どうすればいいんだろう?」

「Timerクラスというのがあるらしいけど、使い方がよく分からない…」

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

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

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

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

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

JavaのTimerとは?

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

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

ポイント

  • アプリケーション起動の5秒後に初期化処理を行う
  • 毎日深夜0時にログファイルを整理する
  • 1分ごとにサーバーの死活監視を行う

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

Timerクラスの基本概要

Timerクラスは、タスクをスケジュールするための目覚まし時計のような役割を担います。この時計に対して「何秒後に」「何分ごとに」といった設定をすると、指定のタイミングで処理を実行してくれます。

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

TimerとTimerTaskの関係

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

  • Timer: いつタスクを実行するかを決めるスケジューラー(目覚まし時計)
  • TimerTask: 実際に実行される処理内容を記述するタスク(やることリスト)

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

Timerの使い方

javatimerの主な使い方は、単発でタスクを実行する方法と、定期的にタスクを実行する方法の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(TimerTask task, long delay, long period)があります。scheduleAtFixedRateとよく似ていますが、こちらは前のタスクが終了した時間を基準に次の実行スケジュールを決定する点が異なります。

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

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

Timerを使ったコード例

それでは、具体的なコードを見ながらjava timerの使い方をマスターしていきましょう。

簡単なサンプルコード

まずは、最もシンプルな例です。プログラムを実行して3秒後(3,000ミリ秒後)に「Hello, Timer!」と表示します。

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つあります。これらを知らないと、予期せぬバグやシステム障害につながる可能性があります。

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

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

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

例外発生時の挙動

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

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

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

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

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

scheduleAtFixedRateの挙動

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

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

System.gc()との関係

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

Timerと他の仕組みの比較

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

ScheduledExecutorServiceとの違い

Java 5から導入されたjava.util.concurrent.ScheduledExecutorServiceは、Timerの欠点を克服した後継APIと位置づけられています。

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

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

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

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

ただし、古いシステムの保守や、ライブラリの内部で使われているケースなど、javatimerのコードを読む機会はまだあります。そのため、Timerの仕組みや注意点を理解しておくことは、Javaエンジニアにとって無駄にはなりません。

まとめ

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

Timerを理解するメリット

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

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

次に学ぶべき関連クラス

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

  • ScheduledExecutorService: Timerのモダンな代替技術です。より堅牢で柔軟なスケジュール管理ができます。
  • ExecutorService: Javaの並列処理の基本となるインターフェースで、スレッドプールを簡単に扱うための仕組みです。
  • CompletableFuture: Java 8から導入された、非同期処理をより高度に、そして簡潔に記述するためのクラスです。

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

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

トム

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

-Java入門