マイナー・マイナー

隠れた名作の発掘が生きがい。

【Java】TimerとScheduledExecutorServiceの例外ハンドリング


スポンサードリンク

TimerやScheduledExecutorServiceといったスケジュール機能を利用すると、タスクを定期的に実行させることができます。しかし、タスク実行中に例外(Exception)が発生すると、スケジュール機能自体が停止してしまう可能性があります。このスケジュール機能停止を避けるための例外ハンドリングの方法をメモしました。

Timerによる定期実行と例外ハンドリング

5回実行するとエラーが生じるタスクを定期実行するサンプルコードを考えます。

App.java

package test.codes._Sample;

import java.util.Timer;

public class App
{
    public static void main( String[] args )
    {
        // Timerを使った周期実行
        Timer timer = new Timer();
        timer.schedule(new SampleTask1(), 0, 1000);
    }
}


SampleTask1.java

package test.codes._Sample;

import java.util.TimerTask;

public class SampleTask1 extends TimerTask {

    private static int counter = 5;

    @Override
    public void run() {
        counter--;
        if (counter < 0) {
            counter = 4;
        }
        System.out.println(24 / counter);
    }

}


実行結果

6
8
12
24
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
     at test.codes._Sample.SampleTask1.run(SampleTask1.java:15)
     at java.util.TimerThread.mainLoop(Timer.java:555)
     at java.util.TimerThread.run(Timer.java:505)


実行すると、1秒ごとに24をcounterで除算した結果が出力されますが、counterが0の時に0除算の例外が発生します。この例外が発生するとTimerは止まってしまいます。なので、Timerを止めないためには、タスクで例外をハンドルする必要があります。


SampleTask1.java (修正版)

package test.codes._Sample;

import java.util.TimerTask;

public class SampleTask1 extends TimerTask {

    private static int counter = 5;

    @Override
    public void run() {
        try {
            counter--;
            if (counter < 0) {
                counter = 4;
            }
            System.out.println(24 / counter);
        } catch (Exception e) {
            counter = 5;
        }
    }

}


実行結果

6
8
12
24
6
8
12
24

ScheduledExecutorServiceによる定期実行と例外ハンドリング

Timerと同様の定期実行処理のサンプルコードです。

App.java

package test.codes._Sample;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class App {
    public static void main(String[] args) {
        // ScheduledExecutorServiceを使った周期実行
        ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1);
        final ExecutorService executor = Executors.newSingleThreadExecutor();

        scheduledExecutor.scheduleAtFixedRate(new Runnable() {
            public void run() {
                Future<Void> future = executor.submit(new SampleTask2());
                try {
                    future.get();
                } catch (Exception e) {
                    future.cancel(true);
                }
            }

        }, 0, 1000, TimeUnit.MILLISECONDS);
    }
}


SampleTask2.java

package test.codes._Sample;

import java.util.concurrent.Callable;

public class SampleTask2 implements Callable<Void> {

    private static int counter = 5;

    public Void call() throws Exception {
        counter--;
        if (counter < 0) {
            counter = 4;
        }
        System.out.println(24 / counter);
        return null;
    }

}


実行結果

6
8
12
24
6
8
12
24


ScheduledExecutorServiceがExecutorServiceを介してタスクを実行します。タスクの進行状況はFutureでチェックできます。タスクの例外は、ScheduledExecutorServiceに設定したRunnableクラスのrunメソッドでハンドルします。future.getでタスクの完了を待ち、例外が発生したらタスクのキャンセルを試みます。


この方法でも、runメソッドの中で例外がスローされると、ScheduledExecutorService自体は止まってしまうので、注意が必要です。


関連:
minor.hatenablog.com