All types of Java applications generally need to programs duplicate tasks. Enterprise applications need to plan a daily log or evening batch process. A J2SE or J2ME calendar application needs to be alarm time according to the user's conventions. However, standard scheduling Timer and Timertask do not have sufficient flexibility and cannot support typically required planned tasks. In this article, Java Developers Tom White show you how to build a simple and universal planning framework for performing any complex planning tasks.
I will collect java.util.timer and java.util.timertask as the Java timer framework, which makes programmers to plan simple tasks (note that these classes can also be used in J2ME). Prior to the introduction of this framework in Java 2 SDK, Standard Edition, Version 1.3, developers must write their own schedule, which requires a lot of energy to handle threads and complex object.wait () methods. However, the Java timer framework does not have enough capabilities to meet the planning requirements of many applications. Even a task that needs to be executed at the same time every day, and you can't use Timer to plan it directly because time jump occurs during the daylight saving time and end. This article shows a universal Timer and Timertask plan framework to allow more flexible planning tasks. This framework is very simple - it includes two classes and an interface - and is easy to master. If you are accustomed to using the Java Timer Frame, you should be able to master this plan framework quickly. Planning a single task plan framework is based on the Java timer framework class. Therefore, in explaining how to use a plan framework and how to implement it, we will first see how to plan these classes. Imagine a cooking egg timer, after a few minutes (this time the egg is boiled) it will make a sound reminder you. The code in Listing 1 forms a simple basic structure of the egg timer, which writes in Java language: List 1. Eggtimer class
Package org.tiling.scheduling.examples;
Import java.util.timer;
Import java.util.timertask;
Public class eviM {
PRIVATE FINAL 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 Plays () {
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 ();
}
}
The EggTimer instance has a Timer instance for providing the necessary plans. After starting the boiled egg timer with the start () method, it plans a Timertask, executed after the specified number of minutes. Time is here, Timer calls Timertask's Start () method in the background, which will make it sound. This application will abort after canceling the timer. Plan repeated task
The Timer can plan a repeated task by specifying a fixed execution frequency or a fixed execution time interval. However, there are many applications to require more complex plans. For example, a alarm clock that is called awakening alert alarm every morning cannot simply use the fixed planning frequency of 86400,000 milliseconds (24 hours), because in those days that are fast or slower (if your time zone uses Summer time), Wake up may be late or too early. The solution is to use the calendar algorithm to calculate the time of the next plan. And this is supported by the plan frame. Consider the ALARMCLOCK implementation in Listing 2:
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.dailyItem;
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 special) {
THIS.HOFDAY = HOUROFDAY;
THIS.MINUTE = Minute;
THIS.SECOND = SECOND;
}
Public void start () {
Scheduler.schedule (New Schedulertask () {
Public void run () {
SoundAlarm ();
}
Private vid 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 ();
}
}
Note this code is very similar to the egg timer application. The AlarmClock instance has a Scheduler instance for providing the necessary plans. This alarm is scheduled to schedule SCHEDULERTASK (not Timertask) to issue alarm. This alarm is not executed after a task is executed after a fixed delay time, but is used to describe its plan with a DailyItemrator class. Here, it is just a planned task performed at 7:00 am every day. Below is an output in normal operation: 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 implemented ScheduleITerator, which is an interface to a series of java.util.date objects to a series of java.util.date objects. Then the next () method iterates the DATE object in order. Return Value NULL will cause task to cancel (ie, it will never run) - this, trying to play an exception again. 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 a DATE object that represents the same time (7:00 am), as shown in Listing 4. So, if next () is called next (), you will get 7:00 AM on the day or after the date of passing to the constructor. Call next () will return 7:00 AM a day, so repeated. In order to achieve this behavior, DailyItemrator uses the Java.util.Calendar instance. The constructor will add one day in the calendar, which makes the first call next () returns the correct Date. Note The code is not explicitly referred to the summertime correction because the Calendar implementation (GregorianCalendar in this case is responsible for processing this, so don't do this.
Listing 4. DailyIterator class
Package org.tiling.scheduling.examples.Item;
Import org.tiling.scheduling.scheduleiterator;
Import java.util.calendar;
Import java.util.date;
/ **
* A Dailyiterator Class Returns a sequence of dates on subsequent days
* Repesenting The Same Time Each Day.
* /
Public class DailyItemrator Implements ScheduleITer {
Private final int hourofday, minute, second;
PRIVATE FINAL.GETARENDAR = Calendar.GetInstance (); Public DailyIterator (int.com) {
THIS (HourofDay, Minute, Second, New Date ());
}
Public DailyITerator (int HourofDay, int minute, int standard, date date) {
THIS.HOFDAY = 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 Plan Framework in the previous section, we have learned how to use the plan frame and compare it with the Java timer framework. Below, I will show you how to implement this framework. In addition to the Scheduleiterator interface shown in Listing 3, there are two other classes of this framework - Scheduler and Schedulertask. These classes actually use Timer and Schedulertask internally because plan is actually a series of single timers.
Listing 5 and 6 show these two categories of source code: 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 itrator) {
This.Schedulertask = schedulertask;
This.iterator = Iterator;
}
Public void run () {
Schedulertask.run ();
Reschadule (Schedulertask, Iterator);
}
}
PRIVATE FINAL TIMER = New Timer ();
Public scheduler () {
}
Public void ca Zancel () {
Timer.cancel ();
}
Public void schedule (Schedulertask Schedulertask,
Scheduleiterator itrator) {
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 ca Zanelled ");
}
Schedulertask.State = schedulertask.scheduled;
Schedulertask.timertask =
New SchedulertImertask (Schedulertask, Iterator);
Timer.schedule (Schedulertask.Timertask, Time);
}
}
}
Private void reschedule (Schedulertask Schedulertask,
Scheduleiterator itrator) {
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:
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 firm 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 = Cancelle;
Return Result;
}
}
Public long scheduledexecutionTime () {
Synchronized (LOCK) {
Return Timertask == NULL? 0: Timertask.scheduledexecutionTime ();
}
}
}
Just like the egg timer, each instance of Scheduler has an instance of Timer for providing the underlying plan. Scheduler did not use a single timer like a cooking egg timer, and it took a set of single timer strings to perform the Schedulertask class in each time specified by ScheduleITOR. Consider the Public Schedule () method on Scheduler - this is a planned entry point because it is a method called by the customer (in the Cancel Task section will describe only another public method Cancel ()). By calling next () of the Scheduleiterator interface, it is found that the time to perform Schedulertask first. Then start the plan at this time by calling a 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 is packaged in tasks and iterator. At the specified time, the Run () method of the embedded class is called, which uses the package task and iterator reference to re-plan the next execution of the task. The reschedule () method is very similar to the Schedule () method, but it is private and executes a slightly different SCHEDULERTASK status check. The replay process repeatedly repeatedly, and constructs a new embedded instance for each plan until the task or scheduler is canceled (or JVM is turned off).
Similar to Timertask, Schedulertask has experienced a series of states in its life cycle. After creating, it is in a virgin state, which indicates that it has never planned. After the plan, it changed to the Scheduled state, and then cancel the task with one of the methods described below, it changes to the Cancelled state. Manage the correct status transformation - If you guarantee that you don't have two plans for a non-VIRGIN state - Added the complexity of the Scheduler and the Schedulertask class. When performing operations that may change the task status, the code must synchronize the lock object of the task.
Cancel the task
There are three ways to cancel the plan task. The first is the Cancel () method that calls Schedulertask. This is like calling Timertask's Cancel () method: The task will never run, but the task that has been run will still run. The return value of the Cancel () method is a Boolean value, indicating whether the scheduled task will run if no Cancel () is called. More specifically, if the task is called Cancel () before calling Cancel (), then it returns True. If you try to plan a canceled (even planned) task again, SCHEDULER will throw an ILLEGALSTATEEXCEPTION.
The second way to cancel the planning task is to let ScheduleITOR returns NULL. This is just a simplified operation of the first way, 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 plan stop time, you use this way to cancel the task.
The third way is to cancel the entire Scheduler by calling its Cancel () method. This will cancel all the tasks of the debugger and make it programs any tasks.
Extending the CRON utility
The plan frame can be used as a UNIX utility, but the specification of the plan is mandatory rather than declaration. For example, in the ALARMCLOCK implementation, the DAILYITERATOR class used, its plan is the same as the cron job plan, which is specified by the crontab item starting with 0 7 * * (these fields respectively specify minutes, hour, month, and weeks. ). However, the plan frame is more flexible than CRON. Imagine a HeatingController app that opens hot water in the morning. I want to indicate it "Open hot water at 8:00 am every working day, and open hot water at 9:00 am." With cron, I need two crontab items (0 8 * 1, 2, 3, 4, 5, and 0 9 * * 6, 7). Use the SCHEDULEITERATOR solution is more concise because I can use the composition to define a single antennament. Listing 7 shows one of the methods:
Listing 7. Define a single iterator with a compound
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 compositeiteiterator
New scheduleiterator [] {
New RestrictedDailyIterator (8, 0, 0, weekdays),
New RestrictedDailyItemrator (9, 0, 0, weekend)
}
);
The RestrictedDailyTeiterator class is very similar to DailyIterator, but it limits to run in a certain day of the week, and a CompositeTeiterator class gets a set of ScheduleITORS and ranks the date to a single plan.
There are many plans to be CRON unable to generate, but the ScheduleITOR is achievable. For example, the plan described in the last day of the month can be implemented with a standard Java calendar algorithm (with Calendar class), and it cannot be expressed in cron. Applications do not need to use the Calendar class. In the source code of this article (see Resources), I joined an example of a security light controller, which is running according to "15 minutes before sunset". This implementation uses Calendrical Calculation Software Package to calculate the sunset time of local (given longitude and latitude).
Real-time guarantee
Be sure to know what the framework has a guarantee when writing a planned application. Is my mission to execute in advance or latency? If there is an advance or delay, how much is the maximum deviation? Unfortunately, there is no simple answer to these issues. However, in practice, its behavior is enough for many applications. The following discussion assumes that the system clock is correct.
Because SCHEDULER will plan to delegate the Timer class, Scheduler can make a real-time guarantee as Timer. Timer Plan with Object.wait (long) method. The current thread should wait until it wakes it, waking up may be 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. In the absence of notification, thread is awakened (called Spurious Wakeup, Joshua Bloch Effective Java Programming Language Guide) describes it.
4. The specified time has arrived.
For Timer classes, the first possibility does not occur because the objects that call Wait () are private. Even so, the Timer implementation is still protected for the top three advanced wake-up, so that the thread wakes up after a specified time. Currently, Object.wait (long) document annotation declares, it will wake up before and after the specified time, so threads may wake up in advance. In this example, Timer will make another wait () execute (ScheduledExecutionTime - System.currentTimeMillis ()) in milliseconds, ensuring that the task will never be executed in advance. Will the task be delayed? Will do. There are two main reasons for delays: thread plans and garbage collection.
Java language norms deliberately do not have strict regulations for threads. This is because the Java platform is universal and for large-scale hardware and its related operating systems. Although most JVM implementation has fair thread schedulers, this is not guaranteed - of course, each implementation has different strategies for the processor time for threads. Therefore, when the TIMER thread wakes up after the assignment time, it actually executes its task depends on the JVM thread plan policy, and how many other threads competition processor time. Therefore, to slow down the delay execution of the task, you should minimize the number of threads running in the application. In order to do this, consider run the scheduler in a separate JVM.
For large applications for creating a large number of objects, JVM spent on garbage collection (GC) will have a lot. By default, when GC is performed, the entire application must wait for it, this may have a few seconds or even longer (the command line option of the Java application launch) -verbose: GC will result in reporting to the console A GC event). To minimize these pauses caused by GC (this may affect the execution of the quick task), the number of objects created by the application should be minimized. Again, run the plan code in a separate JVM is helpful. At the same time, several fine-tuning options can be tried to reduce GC pauses as much as possible. For example, incremental GCs will try to disperse the cost of the main collection to several small collection. Of course, this will reduce the efficiency of GC, but this may be an acceptable price of the time plan.
When is it planned?
If the task itself can monitor and record the instance of all delays, it is helpful for determining whether the task can run on time. Schedulertask is similar to Timertask, there is a ScheduledExecutionTime () method that returns the time that the planned task is recently executed. At the beginning of the RUN () method of the task, the expression system.currenttime () - ScheduledExecutionTime () is determined, allowing you to determine how long the task is delayed (in milliseconds). This value can be recorded in order to generate a distribution statistics for delay execution. You can use this value to determine what action should be taken - for example, if the task is too late, then it may not do anything. In the case of following the above principles, if the application needs more stringent time guarantees, refer to Java's real-time specification. Conclude
In this article, I introduced a simple enhancement of the Java timer framework, which makes flexible planning strategies possible. The new frame is essentially more common CRON - in fact, the cron is implemented as a Scheduleiterator interface to replace simple Java Cron, which is very useful. Although there is no strict real-time guarantee, many of the general Java applications that need to be planned regular tasks can be used to use this frame.
Reference
"
Tuning Garbage Collection with the 1.3.1 Java Virtual Machine "is a very useful article of Sun, which gives a prompt on how to minimize GC pause time.
· To get more information about GC in DeveloperWorks, see the following article:
"
Java Theory and Practice: Simply Collection of Garbage "(October 2003).
"
Mash That Trash "(July 2003).
"
Fine-Tuning Java Garbage Collection Performance "(Jan 2003).
"
SENSIBLE SANITATION, PART 1 "(August 2002).
"
SENSIBLE SANITATION, PART 2 "(Aug 2002).
"
SENSIBLE SANITATION, PART 3 "(Sep 2002).
·in"
Java Theory and Practice: Passing to a certain extent makes everything makes everything "(November 2002), Brian Goetz discussed the Doug LEA's Util.Concurrent library, which is a treasure gallery of concurrent utility classes.
· Another article of Brian Goetz "
Threading Lightly, Part 2: Reducing Contention (September 2001) Analyzed thread bidding and how to reduce it.