Resources
• Download the source code used in this article.
• “Tuning Garbage Collection with the 1.3.1 Java Virtual Machine” is a very useful article from Sun with hints on how to minimize your GC pauses.
For even more information on GC from developerWorks, see the following articles:
- “Java theory and practice: A brief history of garbage collection” (October 2003)
- “Mash that trash” (June 2003)
- “Fine-tuning Java garbage collection performance” (January 2003)
- “Sensible sanitation, Part 1” (August 2002)
- “Sensible sanitation, Part 2” (August 2002)
- “Sensible sanitation, Part 3” (September 2002)
• In “Java theory and practice: Concurrency made simple (sort of)” (developerWorks, November 2002) Brian Goetz discusses Doug Lea’s util.concurrent library, a treasure trove of concurrency utility classes.
• Also by Brian Goetz, “Threading lightly, Part 2: Reducing contention” (developerWorks, September 2001) examines thread contention and how to reduce it.
• Here’s the documentation for Timer and TimerTask in the Java SDK 1.4.
• Jcrontab is another scheduler written in the Java language, with the emphasis on replacing cron.
• The Network Time Protocol (NTP) is a protocol for synchronizing computer clocks.
• The Real-time Specification for Java is an extension to the Java platform that provides real-time guarantees. This site also includes a reference implementation.
• Effective Java Programming Language Guide by Joshua Bloch (Addison-Wesley, 2001) contains excellent advice (for instance, Item 51: Don’t depend on the thread scheduler).
• For everything you want to know about the world’s calendars and the algorithms to work with them, see Calendrical Calculations by Nachum Dershowitz and Edward M. Reingold (Cambridge University Press, 1997).
• Chapter 17, Threads and Locks, of The Java Language Specification, Second Edition by James Gosling, Bill Joy, Guy L. Steele Jr., and Gilad Bracha (Addison-Wesley, 2000) specifies the behavior of the thread scheduler.
• Concurrent Programming in Java: Design Principles and Patterns by Doug Lea (Addison-Wesley, 1999) is a great book that delves into all aspects of concurrency in the Java platform.
• You’ll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.
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.
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
|
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).
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
|
Listing 6 shows the source code for the SchedulerTask class:
Listing 6. SchedulerTask
|
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.
Introduction
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.
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
|
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
|
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:
|
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
|
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
|
If you found this post useful you may also want to check these out:
