Go to Contents Go to Java Page

実験室

 
 

Timer

 
 

スレッドクラスをまだ使っているのですか?

 
 

アプリケーションには何らかの処理を定期的に行うという処理がいろいろとあると思います。例えば、アニメーションやゲーム、サイトを定期的にチェック、ポーリングなどなど、それこそいろいろあると思います。

そんな時、どういう実装をしてますか?

Runnable インタフェースを派生したクラスを使うこともあるでしょう。もしかして Thread クラスの派生クラスを作ってませんか。本当にそのクラスは Kind of Thread になってますか?

でも、そんなことしなくても、もっと簡単に周期処理を行うことができるんです。しかけは java.util.Timer クラスです。

java.util.Timer クラスは J2SE 1.3 で導入されました。アニメーションなんかスレッドなんか使わないで、このクラスを使えば簡単にできるのに、とよく思います。でも、なぜか書籍などのサンプルでは使われていないのはなぜなんでしょう。

というわけで、Thread クラスなんか使わずに繰り返し処理を行う方法を説明していきます。

また、java.util.Timer には同名のクラスがいろいろあるのですが、それについても最後にまとめておきたいと思います。

 

 
 

簡単な使い方

 
 

Timer は 1 回だけ処理を行うものと、周期的に行う処理の両方を行うことができます。どちらの使い方でも、別スレッドで処理されるので、処理がブロックされることはありません。

Timer クラスの生成は引数のないコンストラクタで行います。処理を指定には schedule メソッドを使用します。

両方とも行う処理 (タスク) は java.util.TimerTask クラスの派生クラスで記述します。

1 つの Timer オブジェクトは複数のタスクを扱うことが可能です。JavaDoc には数千のタスクでも大丈夫とかかれていますが、普通はそれほどまではしないでしょう。

また、Timer オブジェクトはスレッドセーフなので、複数のスレッドで同一の Timer オブジェクトを共有して、タスクを処理させることもできます。

1 回だけ処理を行う場合

    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Wake Up");
        }
    });
 
    Timer timer = new Timer();
    timer.schedule(task, 10000L);

schedule メソッドは第 1 引数が TimerTask オブジェクトでタスクを指定し、第 2 引数が処理を実行するまでの時間をミリ秒で指定します。このサンプルだと 10 秒後に "Wake Up" と表示を行います。第 2 引数が 0 の場合はすぐに実行されます。

タスクは無名クラスを使っていますが、普通のクラスでも OK です。

また、schedule メソッドの第 2 引数には Date オブジェクトを直接時間を指定することもできます。

schedule メソッドは実際にタスクが実行されなくても、ブロックされることはありません。ですから、タスクがとても時間のかかる処理でも、それとは関係なく schedule を実行しているスレッドは処理が進められます。

繰り返しの場合

    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Wake Up");
        }
    });
 
    Timer timer = new Timer();
    timer.schedule(task, 10000L, 5000L);

1 回の実行とはことなり、引数が 3 つになります。第 1 引数がタスク、第 2 引数が繰り返しを開始するまでのディレイ、第 3 引数が繰り返し処理の周期です。第 2、第 3 ともミリ秒で指定しますが、第 2 引数は Date オブジェクトでも OK。

このサンプルは 10 秒後から 5 秒おきに "Wake Up" と表示を行います。

 

 
 

厳密な周期処理

 
 

schedule メソッドで実行される繰り返し処理は次のような繰り返しと同じようなイメージになります (あくまでイメージです、実際の処理方法は違います)。

    while (true) {
        doTask(); // 繰り返し行う処理
    
        try {
            Thread.sleep(period); // period [ms] スリープ
        } catch (InterruptedException ex) {
            // 何らかの例外処理
        }
    }

例えば、スリープしているときに GC が行われてしまったら、実際にスリープしている時間は period より長くなっているかもしれません。

アニメーションなんかは少しぐらい実効が遅れてもたかが知れてますが、時計やアラームなどのアプリケーションでは時間が遅れるのはかなり困りものです。

そんなときには scheduleAtFixedRate メソッドを使用します。使い方はschedule メソッドとまったく同じです。

    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Wake Up");
        }
    });
 
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(task, 10000L, 5000L);

schedule メソッドと scheduleAtFixesRate メソッドを比較したのが次の図です。

schedule メソッドと scheduleAtFixedRate の違い

schedule メソッドだと次の処理の開始が "period + 処理の遅延" 分になってしまいますが、scheduleAtFixedRate だと常に period になります。

処理の遅延が延びてしまい、次の処理開始時間になってしまった場合、schedule メソッドだと Task 処理も遅延してしまいますが、scheduleAtFixedRate メソッドだと遅れを取り戻すために続けて Task 処理が行われることがあります。

 

 
 

その他の Timer

 
 

java.util.Timer クラス以外にも、他のパッケージに同名のクラスが存在します。

Swing の javax.swing.Timer クラスは GUI に特化しています。この Timer クラスはイベントモデルでタスクの処理を行います。タスクはイベントリスナ ActionListener を派生させたクラスになります。タスクの指定はコンストラクタか addActionListener メソッドを使用して行います。

繰返し周期ごとに ActionEvent が発生するので、通常のイベント処理と同じようにタスクを処理します。

Swing はスレッドセーフではないので、マルチスレッドで扱うことはできないのですが、この javax.swing.Timer クラスのイベント処理は Swing のイベント処理スレッドで実行されるためマルチスレッドで使用することが可能です。ただし、通常のイベントと同じようにイベント処理は迅速に終了させる必要があります。時間がかかってしまうと、処理されないイベントがどんどんたまってしまいます。

J2SE 以外にも Timer を使うことができます。J2EE のベースは J2SE と同じなので使えるのは当たりまえですが、J2ME でも使えます。

CLDC では MIDP で Timer が定義されています。また、CDC では Foundation Profile に入っています。両方とも J2SE の Timer と同じ使い方です。

Zaurus や Pocket Cosmo などの PDA などにまだ使われている Personal Java にもタイマーがありますが、J2SE の Timer とはちょっと違っていて、PTimer といいます。使い方はどちらかというと Swing の Timer に近く、タイマーイベントが起こるタイプです。

 

 
 

簡単なサンプル

 
 

せっかくなので何かサンプルを作ってみましょう。

Timer といえば時計。というわけで作ってみました。

ソース Clock.java
ClockTask.java
JavaWebStart の JNLP clock.jnlp
   

単にフレームにラベル (JLabel) を貼ってあるだけで、この文字列を Timer を使って変更するだけです。

Timer オブジェクトの初期化は次のように行いました。

    private void initTimer() {
        Timer timer = new Timer();
         
        // 2 秒後からスタート
        Date start = new Date((System.currentTimeMillis() / 1000L) * 1000L + 2000L);
        timer.scheduleAtFixedRate(new ClockTask(this), start, 1000L);
    }

タスクは ClockTask クラスで実行します。単に Clock クラスの update メソッドをコールするだけです。このとき、時間を調べるために TimerTask クラスの scheduledExecutionTime メソッドを使用します。

import java.util.TimerTask;
 
public class ClockTask extends TimerTask {
    private Clock clock;
 
    public ClockTask(Clock clock) {
        this.clock = clock;
    }
 
    public void run() {
        clock.update(scheduledExecutionTime());
    }
}

Clock クラスの update メソッドはラベルの文字列を変更するだけです。clockLabel がラベル、formatter は DateFormat オブジェクトで時間を整形するのにしています。

    public void update(long time) {
        clockLabel.setText(formatter.format(new Date(time)));
    }

実行するときには引数に画像ファイルを指定すると、それを背景にして時計を描画します。ただし、JavaWebStart では使用できません。

 

 
 

最後に

 
 

Timer クラスをしようすることで、マルチスレッドをあまり意識することなく、周期的な処理を行うことができました。でも、マルチスレッドであることを完全に忘れてはいけません。周期的な処理を行うスレッドとメインのスレッドで同じプロパティを参照するときなどは、synchronized を使用して同期化を行う必要があります。

とはいうものの、Thread クラスを直接使ったときよりもずっと簡単に周期処理が実現できるはずです。もっと活用してもいいと思うんだけどなぁ。

 

今回使用したサンプルはここからダウンロードできます。

 

(May. 2003)

 
 
Go to Contents Go to Java Page