Implementation plan framework
In the previous section, we learned how to use the scheduler framework and compared it with the Java timer framework. Next, I will show you how to implement this framework. In addition to the scheduleiterator interface shown in listing 3, The framework consists of two classes: scheduler and schedulertask. These classes actually use timer and schedulertask internally, because the plan is actually a series of single timers.
Listing 5 and 6 show the source code of these 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 of the schedulertask class:
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, each scheduler instance has an instance of timer to provide the underlying plan. Scheduler does not use a single timer as it implements the egg timer. It concatenates a group of single timers to execute the schedulertask class at each time specified by scheduleiterator.
Consider the public schedule () method on schedle -- this is the plan entry point, because it is the method called by the customer (the only other public method cancel () is described in the cancel task section ()). Call next () of the scheduleiterator interface to find the time when schedulertask is executed for the first time. Then, the Start Plan is executed at this time by calling the single schedule () method of the underlying Timer class. The timertask object provided for a single execution is an instance of the embedded schedulertimertask class, which encapsulates tasks and iterator ). Call the run () method of the embedded class at the specified time. It uses the wrapped task and iterator reference to reschedule the next execution of the task. The reschedule () method is very similar to the Schedule () method, except that it is private and executes a slightly different set of schedulertask status checks. The rescheduling process repeats repeatedly and creates a new embedded class instance for each execution plan until the task or scheduler is canceled (or the JVM is disabled ).
Similar to timertask, schedulertask has to go through a series of states in its lifecycle. After it is created, it is in the virgin state, which indicates that it has never been scheduled. After the task is scheduled, it changes to the scheduled state. After canceling the task using one of the methods described below, it changes to the cancelled state. Managing the correct state transition-for example, making sure you do not plan a job in a non-virgin state twice-increases the complexity of the scheduler and schedulertask classes. When performing operations that may change the task status, the Code must synchronize the Lock Object of the task.
Cancel task
There are three methods to cancel a scheduled task. The first method is to call the cancel () method of schedulertask. This is similar to calling the cancel () method of timertask: The task will no longer run, but the running task will still run. The Return Value of the cancel () method is a Boolean value, indicating whether the scheduled task will run if cancel () is not called. More accurately, if a task is in scheduled state before cancel () is called, true is returned. If you try to schedule a canceled (or even scheduled) task again, scheduler will throw an illegalstateexception.
The second way to cancel a scheduled task is to make scheduleiterator return null. This is only the first method to simplify the operation, because the scheduler class calls the cancel () method of the schedulertask class. If you want to use an iterator instead of a task to control the scheduled stop time, you can use this method to cancel the task.
The third method is to call its cancel () method to cancel the entire scheduler. This will cancel all the tasks of the program debugging and make it unable to schedule any more tasks.
Extended cron Utility
You can compare a planning framework to a Unix cron utility, except that the number of planned times is mandatory rather than declarative. For example, the dailyiterator class used in the alarmclock implementation has the same plan as the cron job, all are specified by crontab items starting with 0 7 *** (these fields specify minutes, hours, days, months, and weeks respectively ).
However, the planning framework is more flexible than cron. Imagine a heatingcontroller application that opens hot water in the morning. I would like to instruct it to "Open hot water at AM every workday and open hot water at AM every week ". To use Cron, I need two crontab items (0 8 ** 1, 2, 3, 4, 5, and 0 9 ** 6, 7 ). The scheduleiterator solution is simpler, because I can use composition to define a single iterator. Listing 7 shows one of the methods:
Listing 7. Using Composite 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) } );
|
The restricteddailyiterator class is similar to the dailyiterator class, except that it is limited to run only on a specific day of the week, while a compositeiterator class acquires a set of scheduleiterators and correctly arranges dates in a single plan.
Many plans cannot be generated by Cron, but scheduleiterator can. For example, the plan described on the last day of each month can be implemented using the standard Java calendar algorithm (using the calendar class), but cron cannot express it. Applications do not even need to use the calendar class. In the source code of this article (see references), I added an example of a safety light controller that runs according to the plan "turn on the light 15 minutes before sunset. This implementation uses the calendrical calculations software package to calculate the local Sunset Time (given longitude and latitude.
Real-time assurance
When writing an application of a usage plan, you must understand the time guarantee of the framework. Is my task executed ahead of schedule or delayed? If there is advance or delay, what is the maximum deviation? Unfortunately, there are no simple answers to these questions. However, in practice, its behavior is sufficient for many applications. The following discussion assumes that the system clock is correct.
Because schedmer delegates the plan to the Timer class, schedmer can ensure the same real-time as the timer. Timer uses the object. Wait (long) method to schedule tasks. The current thread will wait until it is awakened, which may be due to one of the following reasons:
1. Another thread calls the notify () or notifyall () method of the object.
2. The thread is interrupted by another thread.
3. Without notice, the thread is awakened (called spurious wakeup, which is described in item 50 in the valid Java programming language guide of Joshua Bloch.
4. The specified time has reached.
For the Timer class, the first possibility will not occur because the objects that call wait () are private. Even so, the timer implementation still protects against the first three reasons for early wakeup, which ensures that the thread will wake up after the specified time. Currently, object. Wait (long) Comments declare that it will wake up before and after the specified time, so the thread may wake up in advance. In this example, timer will let another wait () execute (scheduledexecutiontime-system. currenttimemillis () in milliseconds to ensure that the task will never be executed in advance. Will the task be delayed? Yes. There are two main causes for delayed execution: thread plan and garbage collection.
The Java language specification intentionally does not strictly define the thread plan. This is because the Java platform is universal and applicable to a wide range of hardware and related operating systems. Although most JVM implementations have fair Thread Scheduling programs, this is not guaranteed-of course, each implementation has a different policy for allocating the processor time to the thread. Therefore, when the timer thread wakes up after the allocated time, the time it actually executes its task depends on the JVM thread plan policy and how many other threads compete for the processor time. Therefore, to reduce the latency of tasks, you should minimize the number of running threads in the application. To achieve this, you can consider running the scheduler in a separate JVM.
For large applications that create a large number of objects, the JVM spends a lot of time on garbage collection (GC. By default, the entire application must wait for GC to complete, which may take several seconds or even longer (the command line option of the Java application initiator-verbose: GC will cause every GC event to be reported to the console ). To minimize the GC-caused pause (which may affect the execution of quick tasks), minimize the number of objects created by the application. Similarly, it is helpful to run the plan code in a separate JVM. At the same time, you can try several fine-tuning options to minimize GC pause. For example, incremental GC will try to distribute the primary collection cost to several small collections. Of course this will reduce GC efficiency, but this may be an acceptable price for the time plan.
When is it scheduled?
If the task itself can monitor and record all instances with delayed execution, it is helpful to determine whether the task can run on a running basis. Schedulertask is similar to a timertask. It has a scheduledexecutiontime () method that returns the last execution time of the scheduled task. When the run () method of a task starts, the expression system. currenttimemillis ()-scheduledexecutiontime () is determined to determine how long the task is delayed (in milliseconds ). This value can be recorded to generate a statistics on the distribution of delayed execution. You can use this value to determine what actions the task should take. For example, if the task is too late, it may not do anything. If the application requires more strict time guarantee, you can refer to the Real-Time Java specification.
Conclusion
In this article, I introduced a simple enhancement of the Java timer framework, which makes flexible planning policies possible. The new framework is essentially a more general Cron-in fact, it is very useful to implement cron as a scheduleiterator interface to replace pure Java cron. Although strict real-time assurance is not provided, this framework can be used by many general Java applications that require scheduled tasks.
References
· "Tuning garbage collection with the 1.3.1 Java Virtual Machine" is a very useful article by Sun, which provides tips on how to minimize GC pause times.
· For more information about GC in developerworks, see the following article:
"Java Theory and Practice: A Brief History of garbage collection" (October 2003 ).
"Mash that trash" (July 2003 ).
"Fine-tuning Java garbage collection performance" (August 1, January 2003 ).
"Sensible sanitation, Part 1" (August 1, August 2002 ).
"Sensible sanitation, Part 2" (August 1, August 2002 ).
"Sensible sanitation, Part 3" (August 3, September 2002 ).
· In "Java Theory and Practice: concurrency makes everything simple to some extent" (developerworks, November 2002), Brian Goetz discussed Doug Lea's util. concurrent library, which is a repository of concurrent Utility Classes.
Another article by Brian Goetz, "threading lightly, Part 2: Threading contention" (developerworks, September 2001) analyzes thread contention and how to reduce it.