|
|
< Day Day Up > |
|
11.2 The java.util.Timer ClassThe java.util.Timer class was added to JDK 1.3 specifically to provide a convenient way for tasks to be executed asynchronously. This class allows an object (of a specific class we'll look at) to be executed at a later time. The time can be specified either relative to the current time or as an absolute time. This class also supports the repeated execution of the task. The Timer class executes tasks with a specific interface: public abstract class TimerTask implements Runnable {
protected TimerTask( );
public abstract void run( );
public boolean cancel( );
public long scheduledExecutionTime( );
}Tasks to be executed by the Timer class must inherit from the TimerTask class. As in the Thread class, the task to be executed is the run() method. In fact, the TimerTask class actually implements the Runnable interface. The Timer class requires a TimerTask object so that two methods can be attached to the task; these methods can be used to maintain the task. These methods do not have to be implemented; the TimerTask class provides a default implementation for them. A class that inherits from the TimerTask class need only implement the run() method. The downside of this technique is that the task can't inherit from other classes. Since the TimerTask class is not an interface, it means that tasks have to either be created from classes that don't already inherit from other classes, or wrapper classes have to be created to forward the request. The cancel() method is used to stop the class from being executed. A task that is already executing is unaffected when this method is called. However, if the task is repeating, calling the cancel() method prevents further execution of the class. For tasks that are executed only once, the cancel( ) method returns whether the task has been cancelled: if the task is currently running, has already run, or has been previously cancelled, it returns a boolean value of false. For repeating tasks, this method always returns a boolean value of true. The scheduledExecutionTime() method is used to return the time at which the previous invocation of a repeating task occurred. If the task is currently running, it is the time at which the task began. If the task is not running, it is the time at which the previous execution of the task began. Its purpose is a bit obscure but it will make more sense after we discuss the Timer class. Here is the interface of the Timer class: public class Timer {
public Timer( );
public Timer(boolean isDaemon);
public Timer(String name);
public Timer(String name, boolean isDaemon);
public void schedule(TimerTask task, long delay);
public void schedule(TimerTask task, Date time);
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, Date firstTime, long period);
public void scheduleAtFixedRate(TimerTask task, long delay, long period);
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);
public void cancel( );
public int purge( );
}The Timer class provides the means to execute tasks at a later time. The tasks that are scheduled are placed in an ordered queue and are executed sequentially by a single thread. Four constructors are provided to create different versions of the Timer class. The most important parameter of these constructors allows the definition of whether the created thread is a daemon thread (see Chapter 13). This is useful for tasks which are needed only if the user is still interacting with the program. If the timer thread is a daemon thread, the program can exit when all the user threads terminate. The other parameter is used to name the thread; this is important if the threads are to be monitored by a debugger. The first two overloaded versions of the schedule() method are used to schedule one-time tasks. The first allows for the specification of a delay: a time period in milliseconds relative to the current time. The second allows for the specification of an absolute time. The last two overloaded versions of the schedule() method are used to schedule repeating tasks. The third parameter is used to specify the period in milliseconds between invocations of the repeated tasks. There are a few important issues in the timer implementation, particularly for repeated tasks. First, only a single thread executes the tasks. While it is recommended that the tasks executed by the Timer class be short-lived, no check ensures that this is so. This means that if the Timer object is overwhelmed, a task may be executed at a time much later than the specified time. For repeated tasks, the schedule() method does not take this into account. The schedule time is allowed to drift, meaning that the next iteration of the task is based on the previous iteration. This is not very useful if the task is used to maintain a clock or other time-critical task. Two mechanisms can be used to resolve this. The first mechanism is the two overloaded scheduleAtFixedRate() methods. The schedule() method schedules the next execution of the task based on when the previous execution actually occurred. The next iteration of the task scheduled by the scheduleAtFixedRate() method is calculated from when the previous iteration was supposed to execute梟ot when the previous iteration actually executes. The second mechanism is the scheduledExecutionTime() method of the TimerTask class. This method can be used by the task itself to determine when the task is supposed to run. Based on the comparison to the current time, the task can adjust its behavior. This is even more important when the scheduleAtFixedRate() method is used to schedule the task. Since the tasks are not allowed to drift, more than one iteration of the repeated task may be waiting to execute. As a result, a timer task may want to skip a particular execution if it knows that another execution is pending in the queue. For example, a task that runs every five seconds can tell if it has missed an execution by using this code: public class MyTimerTask extends TimerTask {
public void run( ) {
if (System.currentTimeMillis( ) - scheduledExecutionTime( ) > 5000) {
// We're more than five seconds off; skip this because another task
// will already have been scheduled.
return;
}
...
}
}Table 11-1 shows when tasks would be executed under different scheduling models of the Timer class. In this example, we're assuming that the task is to be run every second, executes for .1 seconds, and the system becomes bogged down for .5 seconds between the second and third iteration. The schedule() method drifts by .5 seconds on subsequent executions. The scheduleAtFixedRate() method runs the delayed iteration .5 seconds late but still executes the remaining iterations according to the original schedule. Neither takes into account the time required to execute the task.
The cancel() method is provided by the Timer class to destroy the timer. All the tasks in the timer are simply cancelled, and no new tasks are allowed to be scheduled. The Timer object can no longer be used to schedule any more tasks. If a task is currently executing, it is allowed to finish; currently executing tasks are not interrupted. The purge() method is used for maintenance. The task's cancel() method does not actually delete the task from the task queue; the task is simply marked as cancelled. The task is deleted from the queue by the timer when it is time for the task to execute: because the task is marked as cancelled, the task is skipped and deleted from the queue at that time. The purge() method is important only when a large number of tasks are being cancelled (or the tasks themselves consume a lot of memory). By purging the timer, the task objects are removed from the queue, allowing them to be garbage collected. 11.2.1 Using the TimerHere's an example that uses the Timer class. The example program allows you to monitor the reachability of one or more web sites: it periodically attempts to retrieve a URL from each web site. Web sites that are reachable are displayed in green; web sites that are down are displayed in red. We start with the timer task that contacts the web site: package javathreads.examples.ch11.example1;
import java.util.*;
import java.net.*;
public class URLPingTask extends TimerTask {
public interface URLUpdate {
public void isAlive(boolean b);
}
URL url;
URLUpdate updater;
public URLPingTask(URL url) {
this(url, null);
}
public URLPingTask(URL url, URLUpdate uu) {
this.url = url;
updater = uu;
}
public void run( ) {
if (System.currentTimeMillis( ) - scheduledExecutionTime( ) > 5000) {
// Let the next task do it
return;
}
try {
HttpURLConnection huc = (HttpURLConnection) url.openConnection( );
huc.setConnectTimeout(1000);
huc.setReadTimeout(1000);
int code = huc.getResponseCode( );
if (updater != null)
updater.isAlive(true);
} catch (Exception e) {
if (updater != null)
updater.isAlive(false);
}
}
}The run() method periodically contacts the given URL and then updates the status watcher depending on whether or not reading the URL was successful. Note that if more than five seconds have elapsed since the last time the task runs, the task skips itself. The program that sets up the task looks like this: package javathreads.examples.ch11.example1;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.swing.*;
import java.util.Timer;
public class URLMonitorPanel extends JPanel implements URLPingTask.URLUpdate {
Timer timer;
URL url;
URLPingTask task;
JPanel status;
JButton startButton, stopButton;
public URLMonitorPanel(String url, Timer t) throws MalformedURLException {
setLayout(new BorderLayout( ));
timer = t;
this.url = new URL(url);
add(new JLabel(url), BorderLayout.CENTER);
JPanel temp = new JPanel( );
status = new JPanel( );
status.setSize(20, 20);
temp.add(status);
startButton = new JButton("Start");
startButton.setEnabled(false);
startButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
makeTask( );
startButton.setEnabled(false);
stopButton.setEnabled(true);
}
});
stopButton = new JButton("Stop");
stopButton.setEnabled(true);
stopButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
task.cancel( );
startButton.setEnabled(true);
stopButton.setEnabled(false);
}
});
temp.add(startButton);
temp.add(stopButton);
add(temp, BorderLayout.EAST);
makeTask( );
}
private void makeTask( ) {
task = new URLPingTask(url, this);
timer.schedule(task, 0L, 5000L);
}
public void isAlive(final boolean b) {
SwingUtilities.invokeLater(new Runnable( ) {
public void run( ) {
status.setBackground(b ? Color.GREEN : Color.RED);
status.repaint( );
}
});
}
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("URL Monitor");
Container c = frame.getContentPane( );
c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS));
Timer t = new Timer( );
for (int i = 0; i < args.length; i++) {
c.add(new URLMonitorPanel(args[i], t));
}
frame.addWindowListener(new WindowAdapter( ) {
public void windowClosing(WindowEvent evt) {
System.exit(0);
}
});
frame.pack( );
frame.show( );
}
}Each individual panel monitors a single URL. Note that the isAlive() method runs from the timer thread, so its invocation of Swing methods is placed within a call to the invokeLater() method. Also note that since a task cannot be reused, the actionPerformed() method associated with the Start button must set up a new task. This application points out the basic shortcomings of the Timer class. We've set it up so that all the panels share a single instance of the timer, which means a single thread. Although our task uses timeouts to talk to the web server, it's conceivable that a single execution of the run() method of the task could take almost two seconds (though it's more likely to take only one second if the site is down). If you monitor 10 sites and your ISP goes down, the single timer thread ends up with a backlog of tasks. That's the reason we put logic into the run() method of the task to check to see whether it missed its scheduled execution time. The alternative is to create a new timer for each panel. In that case, we don't have to worry about a backlog of tasks. The downside is that we now have one thread for every site we're monitoring. That's not a big deal unless we're monitoring thousands of sites, but it's not optimal either. We'll revisit this later in this chapter. |
|
|
< Day Day Up > |
|