Scheduling recurring tasks in Java applications

Introducing a simple generalisation of the Java language's Timer class

All manner of Java applications commonly need to schedule tasks for repeated execution. Enterprise applications need to schedule daily logging or overnight batch processes. A J2SE or J2ME calendar application needs to schedule alarms for a user's appointments. However, the standard scheduling classes, Timer and TimerTask, are not flexible enough to support the range of scheduling tasks typically required. In this article, Java developer Tom White shows you how to build a simple, general scheduling framework for task execution conforming to an arbitrarily complex schedule.

Share:

Tom White, Lead Java Developer, Kizoom

Tom White is Lead Java Developer at Kizoom, a leading UK software company in the delivery of personalised travel information to mobile devices. Clients include the UK's national train operator, the London public transport system, and UK national bus companies. Since its founding in 1999, Kizoom has used all the disciplines from Extreme Programming. Tom has been writing Java programs full time since 1996, using most of the standard and enterprise Java APIs, from client Swing GUIs and graphics to back-end messaging systems. He has a first class honours degree in mathematics from Cambridge University. When not programming, Tom enjoys making his young daughter laugh and watching 1930s Hollywood films. Contact Tom at tom-at-tiling.org.



04 November 2003

Also available in Japanese

The java.util.Timer and java.util.TimerTask classes, which I'll refer to collectively as the Java timer framework, make it easy for programmers to schedule simple tasks. (Note that these classes are also available in J2ME.) Before this framework was introduced in the Java 2 SDK, Standard Edition, Version 1.3, developers had to write their own scheduler, which involved grappling with threads and the intricacies of the Object.wait() method. However, the Java timer framework is not rich enough to meet the scheduling requirements of many applications. Even a task that needs repeating every day at the same time cannot be directly scheduled using Timer, due to the time jumps that occur as daylight saving time comes and goes.

This article presents a scheduling framework that is a generalisation of Timer and TimerTask to allow more flexible scheduling. The framework is very simple -- it consists of two classes and an interface -- and it's easy to learn. If you're used to working with the Java timer framework, you should be able to pick up the scheduling framework very quickly. (For more information about the Java timer framework, see Resources.)

Scheduling a one-shot task

The scheduling framework is built on top of the Java timer framework classes. Therefore, we'll first look at scheduling using these classes before I explain how the scheduling framework is used and how it is implemented.

Imagine an egg timer that tells you when a number of minutes have elapsed (and therefore that your egg is cooked) by playing a sound. The code in Listing 1 forms the basis for a simple egg timer written in the Java language:

Listing 1. EggTimer class
package org.tiling.scheduling.examples;

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

public class EggTimer {
    private final Timer timer = new Timer();
    private final int minutes;

    public EggTimer(int minutes) {
        this.minutes = minutes;
    }

    public void start() {
        timer.schedule(new TimerTask() {
            public void run() {
                playSound();
                timer.cancel();
            }
            private void playSound() {
                System.out.println("Your egg is ready!");
                // Start a new thread to play a sound...
            }
        }, minutes * 60 * 1000);
    }

    public static void main(String[] args) {
        EggTimer eggTimer = new EggTimer(2);
        eggTimer.start();
    }

}

An EggTimer instance owns a Timer instance to provide the necessary scheduling. When the egg timer is started using the start() method, it schedules a TimerTask to execute after the specified number of minutes. When the time is up, the run() method on the TimerTask is called behind the scenes by Timer, which causes it to play a sound. The application then terminates after the timer is cancelled.


Scheduling a recurring task

Timer allows tasks to be scheduled for repeated execution by specifying a fixed rate of execution or a fixed delay between executions. However, there are many applications that have more complex scheduling requirements. For example, an alarm clock that sounds a wake-up call every morning at the same time cannot simply use a fixed rate schedule of 86400000 milliseconds (24 hours), because the alarm would be too late or early on the days the clocks go forward or backward (if your time zone uses daylight saving time). The solution is to use calendar arithmetic to calculate the next scheduled occurrence of a daily event. This is precisely what the scheduling framework supports. Consider the AlarmClock implementation in Listing 2 (see Resources to download the source code for the scheduling framework, as well as a JAR file containing the framework and examples):

Listing 2. AlarmClock class
package org.tiling.scheduling.examples;

import java.text.SimpleDateFormat;

import java.util.Date;

import org.tiling.scheduling.Scheduler;
import org.tiling.scheduling.SchedulerTask;
import org.tiling.scheduling.examples.iterators.DailyIterator;

public class AlarmClock {

    private final Scheduler scheduler = new Scheduler();
    private final SimpleDateFormat dateFormat =
        new SimpleDateFormat("dd MMM yyyy HH:mm:ss.SSS");
    private final int hourOfDay, minute, second;

    public AlarmClock(int hourOfDay, int minute, int second) {
        this.hourOfDay = hourOfDay;
        this.minute = minute;
        this.second = second;
    }

    public void start() {
        scheduler.schedule(new SchedulerTask() {
            public void run() {
                soundAlarm();
            }
            private void soundAlarm() {
                System.out.println("Wake up! " +
                    "It's " + dateFormat.format(new Date()));
                // Start a new thread to sound an alarm...
            }
        }, new DailyIterator(hourOfDay, minute, second));
    }

    public static void main(String[] args) {
        AlarmClock alarmClock = new AlarmClock(7, 0, 0);
        alarmClock.start();
    }
}

Notice how similar the code is to the egg timer application. The AlarmClock instance owns a Scheduler instance (rather than a Timer) to provide the necessary scheduling. When started, the alarm clock schedules a SchedulerTask (rather than a TimerTask) to play the alarm. And instead of scheduling the task for execution after a fixed delay, the alarm clock uses a DailyIterator class to describe its schedule. In this case, it simply schedules the task at 7:00 AM every day. Here is the output from a typical run:

Wake up! It's 24 Aug 2003 07:00:00.023
Wake up! It's 25 Aug 2003 07:00:00.001
Wake up! It's 26 Aug 2003 07:00:00.058
Wake up! It's 27 Aug 2003 07:00:00.015
Wake up! It's 28 Aug 2003 07:00:00.002
...

DailyIterator implements ScheduleIterator, an interface that specifies the scheduled execution times of a SchedulerTask as a series of java.util.Date objects. The next() method then iterates over the Date objects in chronological order. A return value of null causes the task to be cancelled (that is, it will never be run again) -- indeed, an attempt to reschedule will cause an exception to be thrown. Listing 3 contains the ScheduleIterator interface:

Listing 3. ScheduleIterator interface
package org.tiling.scheduling;

import java.util.Date;

public interface ScheduleIterator {
    public Date next();
}

DailyIterator's next() method returns Date objects that represent the same time each day (7:00 AM), as shown in Listing 4. So if you call next() on a newly constructed DailyIterator class, you will get 7:00 AM of the day on or after the date passed into the constructor. Subsequent calls to next() will return 7:00 AM on subsequent days, repeating forever. To achieve this behavior DailyIterator uses a java.util.Calendar instance. The constructor sets up the calendar so that the first invocation of next() returns the correct Date simply by adding a day onto the calendar. Note that the code contains no explicit reference to daylight saving time corrections; it doesn't need to because the Calendar implementation (in this case GregorianCalendar) takes care of this.

Listing 4. DailyIterator class
package org.tiling.scheduling.examples.iterators;

import org.tiling.scheduling.ScheduleIterator;

import java.util.Calendar;
import java.util.Date;

/**
 * A DailyIterator class returns a sequence of dates on subsequent days
 * representing the same time each day.
 */
public class DailyIterator implements ScheduleIterator {
    private final int hourOfDay, minute, second;
    private final Calendar calendar = Calendar.getInstance();

    public DailyIterator(int hourOfDay, int minute, int second) {
        this(hourOfDay, minute, second, new Date());
    }

    public DailyIterator(int hourOfDay, int minute, int second, Date date) {
        this.hourOfDay = hourOfDay;
        this.minute = minute;
        this.second = second;
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, 0);
        if (!calendar.getTime().before(date)) {
            calendar.add(Calendar.DATE, -1);
        }
    }

    public Date next() {
        calendar.add(Calendar.DATE, 1);
        return calendar.getTime();
    }

}

Implementing the scheduling framework

In the previous section, we learned how to use the scheduling framework and compared it with the Java timer framework. Next, I'll show you how the framework is implemented. In addition to the ScheduleIterator interface shown in Listing 3, there are two other classes -- Scheduler and SchedulerTask -- that make up the framework. These classes actually use Timer and TimerTask under the covers, since a schedule is really no more than a series of one-shot timers. Listings 5 and 6 show the source code for the two classes:

Listing 5. Scheduler
package org.tiling.scheduling;

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

public class Scheduler {

    class SchedulerTimerTask extends TimerTask {
        private SchedulerTask schedulerTask;
        private ScheduleIterator iterator;
        public SchedulerTimerTask(SchedulerTask schedulerTask,
                ScheduleIterator iterator) {
            this.schedulerTask = schedulerTask;
            this.iterator = iterator;
        }
        public void run() {
            schedulerTask.run();
            reschedule(schedulerTask, iterator);
        }
    }

    private final Timer timer = new Timer();

    public Scheduler() {
    }

    public void cancel() {
        timer.cancel();
    }

    public void schedule(SchedulerTask schedulerTask,
            ScheduleIterator iterator) {

        Date time = iterator.next();
        if (time == null) {
            schedulerTask.cancel();
        } else {
            synchronized(schedulerTask.lock) {
                if (schedulerTask.state != SchedulerTask.VIRGIN) {
                  throw new IllegalStateException("Task already 
                  scheduled " + "or cancelled");
                }
                schedulerTask.state = SchedulerTask.SCHEDULED;
                schedulerTask.timerTask =
                    new SchedulerTimerTask(schedulerTask, iterator);
                timer.schedule(schedulerTask.timerTask, time);
            }
        }
    }

    private void reschedule(SchedulerTask schedulerTask,
            ScheduleIterator iterator) {

        Date time = iterator.next();
        if (time == null) {
            schedulerTask.cancel();
        } else {
            synchronized(schedulerTask.lock) {
                if (schedulerTask.state != SchedulerTask.CANCELLED) {
                    schedulerTask.timerTask =
                        new SchedulerTimerTask(schedulerTask, iterator);
                    timer.schedule(schedulerTask.timerTask, time);
                }
            }
        }
    }

}

Listing 6 shows the source code for the SchedulerTask class:

Listing 6. SchedulerTask
package org.tiling.scheduling;

import java.util.TimerTask;

public abstract class SchedulerTask implements Runnable {

    final Object lock = new Object();

    int state = VIRGIN;
    static final int VIRGIN = 0;
    static final int SCHEDULED = 1;
    static final int CANCELLED = 2;

    TimerTask timerTask;

    protected SchedulerTask() {
    }

    public abstract void run();

    public boolean cancel() {
        synchronized(lock) {
            if (timerTask != null) {
                timerTask.cancel();
            }
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }

    public long scheduledExecutionTime() {
        synchronized(lock) {
         return timerTask == null ? 0 : timerTask.scheduledExecutionTime();
        }
    }

}

Like the egg timer, every instance of Scheduler owns an instance of Timer to provide the underlying scheduling. Instead of the single one-shot timer used to implement the egg timer, Scheduler strings together a chain of one-shot timers to execute a SchedulerTask class at the times specified by a ScheduleIterator.

Consider the public schedule() method on Scheduler -- this is the entry point for scheduling because it is the method a client calls. (The only other public method, cancel(), is described in Canceling tasks.) The time of the first execution of the SchedulerTask is discovered by calling next() on the ScheduleIterator interface. The scheduling is then kicked off by calling the one-shot schedule() method on the underlying Timer class for execution at this time. The TimerTask object supplied for one-shot execution is an instance of the nested SchedulerTimerTask class, which packages up the task and the iterator. At the allotted time, the run() method is called on the nested class, which uses the packaged task and iterator references to reschedule the next execution of the task. The reschedule() method is very similar to the schedule() method, except that it is private and performs a slightly different set of state checks on SchedulerTask. The rescheduling process repeats indefinitely, constructing a new nested class instance for each scheduled execution, until the task or the scheduler is cancelled (or the JVM shuts down).

Like its counterpart TimerTask, SchedulerTask goes through a series of states during its lifetime. When created, it is in a VIRGIN state, which simply means it has never been scheduled. Once scheduled, it shifts to a SCHEDULED state, then later to a CANCELLED state if the task is cancelled by one of the methods described below. Managing the correct state transitions, such as ensuring that a non-VIRGIN task is not scheduled twice, adds extra complexity to the Scheduler and SchedulerTask classes. Whenever an operation is performed that might change the state of the task, the code must synchronize on the task's lock object.

Canceling tasks

There are three ways to cancel a scheduled task. The first is to call the cancel() method on the SchedulerTask. This is like calling cancel() on a TimerTask: the task will never run again, although it will run to completion if already running. The return value of the cancel() method is a boolean that indicates whether further scheduled tasks might have run had cancel() not been called. More precisely, it returns true if the task was in a SCHEDULED state immediately prior to calling cancel(). If you try to reschedule a cancelled (or even scheduled) task, Scheduler throws an IllegalStateException.

The second way to cancel a scheduled task is for ScheduleIterator to return null. This is simply a shortcut for the first way, as the Scheduler class calls cancel() on the SchedulerTask class. Canceling a task this way is useful if you want the iterator -- rather than the task -- to control when the scheduling stops.

The third way is to cancel the whole Scheduler by calling its cancel() method. This cancels all the scheduler's tasks and leaves it in a state where no more tasks may be scheduled on it.


Extending the cron facility

The scheduling framework could be likened to the UNIX cron facility, except that the specification of scheduling times is controlled imperatively rather than declaratively. For example, the DailyIterator class used in the implementation of AlarmClock has the same scheduling as a cron job, specified by a crontab entry beginning 0 7 * * *. (The fields specify minute, hour, day of month, month, and day of week, respectively.)

However, the scheduling framework has more flexibility than cron. Imagine a HeatingController application that switches the hot water on in the mornings. I would like to instruct it to "turn the hot water on at 8:00 AM on weekdays and 9:00 AM on weekends." Using cron, I would need two crontab entries (0 8 * * 1,2,3,4,5 and 0 9 * * 6,7). By using a ScheduleIterator, the solution is more elegant because I can define a single iterator using composition. Listing 7 shows one way to do this:

Listing 7. Using composition to define a single iterator
    int[] weekdays = new int[] {
        Calendar.MONDAY,
        Calendar.TUESDAY,
        Calendar.WEDNESDAY,
        Calendar.THURSDAY,
        Calendar.FRIDAY
    };
    int[] weekend = new int[] {
        Calendar.SATURDAY,
        Calendar.SUNDAY
    };
    ScheduleIterator i = new CompositeIterator(
        new ScheduleIterator[] {
            new RestrictedDailyIterator(8, 0, 0, weekdays),
            new RestrictedDailyIterator(9, 0, 0, weekend)
        }
    );

A RestrictedDailyIterator class is like DailyIterator, except it is restricted to run on particular days of the week; and a CompositeIterator class takes a set of ScheduleIterators and correctly orders the dates into a single schedule. See Resources for the source code to these classes.

There are many other schedules that cron cannot produce, but an implementation of ScheduleIterator can. For instance, the schedule described by "the last day of every month" can be implemented using standard Java calendar arithmetic (using the Calendar class), whereas it is impossible to express this using cron. Applications don't even have to use the Calendar class. In the source code for this article (see Resources), I have included an example of a security light controller that runs to the schedule "turn the lights on 15 minutes before sunset." The implementation uses the Calendrical Calculations Software Package (see Resources) to compute the time of the sunset locally (given the latitude and longitude).


Real-time guarantees

When writing applications that use scheduling, it is important to understand what the framework promises in terms of timeliness. Will my tasks be executed early or late? If so, what's the maximum margin of error? Unfortunately, there are no simple answers to these questions. However, in practice the behavior is good enough for a large class of applications. The discussion below assumes that the system clock is correct (see Resources for information on the Network Time Protocol).

Because Scheduler delegates its scheduling to the Timer class, the real-time guarantees that Scheduler can make are identical to those of Timer. Timer schedules tasks using the Object.wait(long) method. The current thread is made to wait until it is woken up, which can happen for one of these reasons:

  1. The notify() or notifyAll() method is called on the object by another thread.
  2. The thread is interrupted by another thread.
  3. The thread is woken up in the absence of a notify (known as a spurious wakeup, described in Item 50 of Joshua Bloch's Effective Java Programming Language Guide -- see Resources).
  4. The specified amount of time has elapsed.

The first possibility cannot occur for the Timer class because the object that wait() is called on is private. Even so, the implementation of Timer safeguards against the first three causes of early wakeup, thus ensuring that the thread wakes up after the time has elapsed. Now, the documentation comment for Object.wait(long) states it may wake up after the time has elapsed "more or less", so it is possible that the thread wakes up early. In this case, Timer issues another wait() for (scheduledExecutionTime - System.currentTimeMillis()) milliseconds, thereby guaranteeing that tasks can never be executed early.

Can tasks be executed late? Yes. There are two main causes of late execution: thread scheduling and garbage collection.

The Java language specification is purposefully vague on thread scheduling. This is because the Java platform is general purpose and targets a wide range of hardware and associated operating systems. While most JVM implementations have a thread scheduler that is fair, it is by no means guaranteed -- certainly implementations have different strategies for allocating processor time to threads. Therefore, when a Timer thread wakes up after its allotted time, the time at which it actually executes its task depends on the JVM's thread scheduling policy, as well as how many other threads are contending for processor time. Therefore, to mitigate late task execution, you should minimize the number of runnable threads in your application. It is worth considering running schedulers in a separate JVM to achieve this.

The time that the JVM spends performing garbage collection (GC) can be significant for large applications that create lots of objects. By default, when GC occurs the whole application must wait for it to finish, which may take several seconds or more. (The command line option -verbose:gc for the java application launcher will cause each GC event to be reported to the console.) To minimize pauses due to GC, which may hinder prompt task execution, you should minimize the number of objects your application creates. Again, this is helped by running your scheduling code in a separate JVM. Also, there are a number of tuning options that you can try to minimize GC pauses. For instance, incremental GC attempts to spread the cost of the major collections over several minor collections. The trade-off is that this reduces the efficiency of GC, but this might be an acceptable price for timelier scheduling. (See Resources for more GC tuning hints.)

When was I scheduled?

To determine whether tasks are being run in a timely manner, it helps if the tasks themselves monitor and record any instances of late execution. SchedulerTask, like TimerTask, has a scheduledExecutionTime() method that returns the time that the most recent execution of this task was scheduled to occur. Evaluating the expression System.currentTimeMillis() - scheduledExecutionTime() at the beginning of the task's run() method lets you determine how late the task was executed, in milliseconds. This value could be logged to produce statistics on the distribution of late execution. The value might also be used to decide what action the task should take -- for example, if the task is too late, it might do nothing. If, after following the guidelines above, your application requires stricter guarantees of timeliness, consider looking at the Real-time Specification for Java (see Resources for more information).


Conclusion

In this article, I have introduced a simple enhancement to the Java timer framework that permits very flexible scheduling strategies. The new framework is essentially a generalisation of cron -- in fact, it would be valuable to implement cron as a ScheduleIterator interface to provide a pure Java cron replacement. While not offering strict real-time guarantees, the framework is applicable to a host of general purpose Java applications that need to schedule tasks on a regular basis.


Download

DescriptionNameSize
Sample code for articlej-schedule.zip225KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10884
ArticleTitle=Scheduling recurring tasks in Java applications
publish-date=11042003