Chapter 11. Task Scheduling

In the previous chapter, we examined an interesting aspect of threads. Before we used a thread pool, we were concerned with creating, controlling, and communicating between threads. With a thread pool, we were concerned with the task that we wanted to execute. Using an executor allowed us to focus on our program’s logic instead of writing a lot of thread-related code.

In this chapter, we examine this idea in another context. Task schedulers give us the opportunity to execute particular tasks at a fixed point in time in the future (or, more correctly, after a fixed point in time in the future). They also allow us to set up repeated execution of tasks. Once again, they free us from many of the low-level details of thread programming: we create a task, hand it off to a task scheduler, and don’t worry about the rest.

Java provides different kinds of task schedulers. Timer classes execute tasks (perhaps repeatedly) at a point in the future. These classes provide a basic task scheduling feature. J2SE 5.0 has a new, more flexible task scheduler that can be used to handle many tasks more effectively than the timer classes. In this chapter, we’ll look into all of these classes.

Interestingly, this is not the first time that we have been concerned with when a task is to be executed. Previously, we’ve just considered the timing as part of the task. We’ve seen tools that allow threads to wait for specific periods of time. Here is a quick review:

The sleep() method

In our discussion of the Thread class, we examined the concept of a thread waiting for a specific period of time. The purpose was either to allow other threads to accomplish related tasks, to allow external events to happen during the sleeping period, or to repeat a task periodically. The tasks that are listed after the sleep() method are executed at a later time period. In effect, the sleep() method controls when those tasks are executed.

The join() method

Our discussion of this method of the Thread class represents the first time that we examined alternate tasks to be executed at a later time. The goal of this method is to wait for a specific event—a thread termination. However, the expected thread termination event may not arrive, at least not within the desired time period, so the join() method provides a timeout. This allows the method to return—either by the termination of the thread or by the expiration of the timeout—thus allowing the program to execute an alternate task at a specific time and in a particular situation.

The wait() method

The wait() method of the Object class allows a thread to wait for any event. This method also provides the option to return if a specific time period passes. This allows the program to execute a task at a later time if the event occurs or to specify the exact time to execute an alternate task if the event does not occur. This functionality is also emulated with condition variables using the await() method.

The TimeUnit class

This class is used to define a time period, allowing methods to specify a time period in units other than milliseconds or nanoseconds. This class is used by many of the classes added in J2SE 5.0 to specify a time period for a timeout. This class also provides convenience methods to support certain periodic requests—specifically, it provides alternate implementations of the sleep(), join(), and wait() methods that use a TimeUnit object as their timeout argument.

The DelayQueue class

Our discussion of the DelayQueue class in Chapter 8 is the first time we encounter a class that allows data to be processed at a specific time. When a producer places data in a delay queue, it is not readable by consumers until after a specific period passes. In effect, the task to process the data is to be executed at a later time—a time period that is specified by the data itself.

As these examples show, in some cases, a program needs to execute code only after a specific event or after a period of time. Much of the time, the functionality is indirect in that the timeout is not expected to occur. Java also supports timeout functions directly by providing tools that allow the program to execute specific tasks at a specific time.

We’ve used these methods in our examples when a program needs to execute code only after a specific event or after a period of time. The timing in these cases has always been provided as a timeout value: after a certain period of time, the thread would regain control and be able to execute the appropriate task. However, in this case control always resides with the thread: execution of the appropriate task is synchronous with respect to the code being executed. Java also supports asynchronous task execution in alternate threads; it’s that type of execution that we’ll examine in the remainder of this chapter.

The 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—not 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.

Here’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.

As we’ve discussed, Swing objects cannot be accessed from arbitrary threads—which includes the threads from the Timer class (and the threads in the thread pool of the ScheduledThreadPoolExecutor class that we discuss later in this chapter). We know that we can use the invokeLater() and invokeAndWait() methods of the SwingUtilities class to overcome this, but Java also provides a Timer class just for Swing objects. The javax.swing.Timer class provides the ability to execute actions at a particular time, and those actions are invoked on the event-dispatching thread.

Here is the interface to the javax.swing.Timer class:

public class Timer {
    public Timer(int delay, ActionListener listener);

    public void addActionListener(ActionListener listener);
    public void removeActionListener(ActionListener listener);
    public ActionListener[] getActionListeners( );
    public EventListener[] getListeners(Class listenerType);

    public static void setLogTimers(boolean flag);
    public static boolean getLogTimers( );

    public void setDelay(int delay);
    public int getDelay( )
    public void setInitialDelay(int initialDelay);
    public int getInitialDelay( );

    public void setRepeats(boolean flag);
    public boolean isRepeats( );

    public void setCoalesce(boolean flag);
    public boolean isCoalesce( );

    public void start( );
    public boolean isRunning( );
    public void stop( );
    public void restart( );
}

This class is not really a generic scheduler. In fact, even though multiple callbacks (event listeners) can be attached to the timer, it has only one schedule: all the listeners use the schedule defined by the Timer class itself (rather than the schedule defined by particular tasks). Tasks that require a different schedule need a different instance of the Swing timer. Most of the methods provided by this class are used to configure the schedule and control the timer.

Unlike the java.util.Timer class, this Timer class uses the ActionListener interface. This provides an interface that Swing developers are accustomed to: all Swing objects use event listeners to execute callbacks. When a scheduled time is reached, it is treated as any other event (such as a button press): the registered action listeners are called.

The constructor to the class takes two parameters. The first is the delay in milliseconds. This value is used by the timer as both the initial time to wait to fire the first action listener and the time to wait between repeated firings of the action listeners. The second parameter is an action listener to fire. Both of these parameters can be modified at a later time.

The addActionListener() and removeActionListener() methods are used to add listeners to and remove listeners from the timer. The getActionListeners() method is used to retrieve the listeners that have been registered to the timer. The getListeners() method provides the added qualification of the event listener type. This allows the developer to get specific types of listeners that are registered to the timer. In most cases, this is probably not very useful, as the limitation of the timer as a generic scheduler also limits the number of action listeners registered to each timer.

The getDelay() and setDelay() methods are used to retrieve and modify the time between repeated events (which by default is set in the constructor). This allows it to be different from the initial delay time. That delay time is handled by the getInitialDelay() and setInitialDelay() methods.

The isRepeats() and setRepeats() methods are used to control whether events are repeated. By default, the timer repeats events, as this Timer class was originally designed for tasks such as a blinking cursor. The isCoalesce( ) and setCoalesce() methods are used to handle repeated methods that are backlogged. For example, if a method is to be called once every second, and three seconds have elapsed, then the listener may have to be called three times. If the coalesce flag is set, the listener is called only once. This is important for tasks such as blinking the cursor. If the timer has already missed two blinks, blinking three times very fast does not fix the problem; it is better to just skip the missed blinks.

The getLogTimers() and setLogTimers() methods are used to control debugging of the timer. If debugging is activated, messages are sent to standard output to report the actions of the timer.

Finally, the timer must be activated upon completion of the registration of the listeners (and, possibly, adjusting the initial delay and repeat times). This is accomplished by the start() method. The timer can later be terminated by calling the stop() method. The restart() method resets the timer: the timer then waits until its initial delay time period has elapsed, at which point it starts calling its listeners. The isRunning() method is used to determine whether the timer has been started.

We can use the javax.swing.Timer class in our typing program. Previously, our animated canvas set up a thread to handle the animation; this thread periodically told the animation canvas to repaint itself. Now, we’ll use a timer.

package javathreads.examples.ch11.example2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import javathreads.examples.ch11.*;

public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
             implements ActionListener, CharacterListener {

    private int curX;
    private Timer timer;

    public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
        super(cs);
        timer = new Timer(100, this);
    }

    public synchronized void newCharacter(CharacterEvent ce) {
        curX = 0;
        tmpChar[0] = (char) ce.character;
        repaint( );
    }

    public synchronized void paintComponent(Graphics gc) {
        if (tmpChar[0] == 0)
            return;
        Dimension d = getSize( );
        int charWidth = fm.charWidth(tmpChar[0]);
        gc.clearRect(0, 0, d.width, d.height);
        gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
        if (curX > d.width - charWidth)
            curX = 0;
    }

    public void actionPerformed(ActionEvent ae) {
        repaint( );
    }

    public void setDone(boolean b) {
        if (!b)
           timer.start( );
        else timer.stop( );
    }
}

Note that this implementation is much simpler than our previous implementations. Previously, we set up a thread in the setDone() method; now, we simply call the timer start() method.

Using the timer has also allowed us to simplify the locking around the calls to the repaint() method. Knowing when the animation should run used to require a wait-and-notify mechanism (or condition variable). Now we just defer that to the timer. The Timer class itself has the waiting logic within it: operationally, we haven’t saved anything. But in terms of development, using a timer has saved us some effort. This is a clear example of why using higher-level thread constructs makes things simpler for the developer.

J2SE 5.0 introduced the ScheduledThreadPoolExecutor class, which solves many problems of the Timer class. In many regards, the Timer class can be considered obsolete because of the ScheduledThreadPoolExecutor class. Why is this class needed? Let’s examine some of the problems with the Timer class.

First, the Timer class starts only one thread. While it is more efficient than creating a thread per task, it is not an optimal solution. The optimal solution may be to use a number of threads between one thread for all tasks and one thread per task. In effect, the best solution is to place the tasks in a pool of threads. The number of threads in the pool should be assignable during construction to allow the program to determine the optimal number of threads in the pool.

Second, the TimerTask class is not necessary. It is used to attach methods to the task itself, providing the ability to cancel the task and to determine the last scheduled time. This is not necessary: it is possible for the timer itself to maintain this information. It also restricts what can be considered a task. Classes used with the Timer class must extend the TimerTask class; this is not possible if the class already inherits from another class. It is much more flexible to allow any Runnable object to be used as the task to be executed.

Finally, relying upon the run() method is too restrictive for tasks. While it is possible to pass parameters to the task—by using parameters in the constructor of the task—there is no way to get any results or exceptions. The run() method has no return variable, nor can it throw any type of exceptions other than runtime exceptions (and even if it could, the timer thread wouldn’t know how to deal with it).

The ScheduledThreadPoolExecutor class solves all three of these problems. It uses a thread pool (actually, it inherits from the thread pool class) and allows the developer to specify the size of the pool. It stores tasks as Runnable objects, allowing any task that can be used by the thread object to be used by the executor. Because it can work with objects that implement the Callable interface, it eliminates the restrictive behavior of relying solely on the Runnable interface.

Here’s the interface of the ScheduledThreadPoolExecutor class itself:

public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor {
    public ScheduledThreadPoolExecutor(int corePoolSize);
    public ScheduledThreadPoolExecutor(int corePoolSize,
                              ThreadFactory threadFactory);
    public ScheduledThreadPoolExecutor(int corePoolSize,
                              RejectedExecutionHandler handler);
    public ScheduledThreadPoolExecutor(int corePoolSize,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                    long delay, TimeUnit unit);

    public ScheduledFuture<V> scheduleAtFixedRate(Runnable command,
                    long initialDelay, long period, TimeUnit unit);
    public ScheduledFuture<V> scheduleWithFixedDelay(
                    Runnable command, long initialDelay,
                    long delay, TimeUnit unit);

    public void execute(Runnable command);

    public void shutdown( );
    public List shutdownNow( );
            
    public void setContinueExistingPeriodicTasksAfterShutdownPolicy(
                                   boolean value);
    public boolean getContinueExistingPeriodicTasksAfterShutdownPolicy( );
    public void setExecuteExistingDelayedTasksAfterShutdownPolicy(
                                   boolean value);
    public boolean getExecuteExistingDelayedTasksAfterShutdownPolicy( );
}

The ScheduledThreadPoolExecutor class provides four constructors to create an object. These parameters are basically the same parameters as the thread pool constructors since this executor inherits from the thread pool executor. Therefore, this class is also a thread pool, meaning that some of the parameters assigned by these constructors can also be retrieved and modified by the methods of the ThreadPoolExecutor class.

Note, however, that the constructors have no parameter to specify the maximum number of threads or the type of queue the thread pool should use. The scheduled executor always uses an unbounded queue for its tasks, and the size of its thread pool is always fixed to the number of core threads. The number of core threads, however, can still be modified by calling the setCorePoolSize() method.

The schedule() method is used to schedule a one-time task. You can use the ScheduledFuture object returned by this method to perform the usual tasks on the callable object: you can retrieve its result (using the get() method), cancel it (using the cancel( ) method), or see if it has completed execution (using the isDone() method).

The scheduleAtFixedRate() method is used to schedule a repeated task that is not allowed to drift. This is basically the same scheduling model as the scheduleAtFixedRate() method of the Timer class.

The scheduleWithFixedDelay() method is used to schedule a repeated task where the period between the tasks remains constant; this is useful when the delay between iterations is to be fixed. For instance, this model is better for animation since there is no reason to have animation cycles accumulate if the start times drift. If one cycle of the animation runs late, there is no advantage to running the next cycle earlier.

Table 11-2 shows when tasks would be executed under different scheduling models of the ScheduledThreadPoolExecutor class. In this example, we’re again 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 scheduleAtFixedRate() method runs the delayed iteration .5 seconds late but still executes the remaining iterations according to the original schedule (exactly the same as the java.util.Timer class). The scheduleWithFixedDelay() method takes into account the execution time of the task; this is why each iteration drifts by .1 seconds. It does not compensate for the .5-second delay, so it drifts over time.

The execute() and submit() methods are used to schedule a task to run immediately. These methods are present mainly because the Executor interface requires them. Still, it may be useful for one task to add other tasks to be run in the pool rather than execute them directly, because then the primary task doesn’t own the thread in the pool for a huge period of time. It also allows the thread pool to assign the subtasks to other threads in the pool if the pool is not busy.

The shutdown() and shutdownNow() methods are also part of the thread pool class. The shutdown() method is used to shut down the executor but allows all pending tasks to complete. The shutdownNow() method is used to try to cancel the tasks in the pool in addition to shutting down the thread pool. However, this works differently from a thread pool because of repeating tasks. Since certain tasks repeat, tasks could technically run forever during a graceful shutdown.

To solve this, the task executor provides two policies. The ExecuteExistingDelayedTasksAfterShutdownPolicy is used to determine whether the tasks in the queue should be cancelled upon graceful shutdown. The ContinueExistingPeriodicTasksAfterShutdownPolicy is used to determine whether the repeating tasks in the queue should be cancelled upon graceful shutdown. Therefore, setting both to false empties the queue but allows currently running tasks to complete. This is similar to how the Timer class is shut down. The shutdownNow() method cancels all the tasks and also interrupts any task that is already executing.

With the support of thread pools, callable tasks, and fixed delay support, you might conclude that the Timer class is obsolete. However, the Timer class has some advantages. First, it provides the option to specify an absolute time. Second, the Timer class is simpler to use: it may be preferable if only a few tasks or repeated tasks are needed.

Here’s a modification of our URL monitor that uses a scheduled executor. Modification of the task itself means a simple change to the interface it implements:

package javathreads.examples.ch11.example3;
...
public class URLPingTask implements Runnable {
    ...
}

Our Swing component has just a few changes:

package javathreads.examples.ch11.example3;
...
import java.util.concurrent.*;

public class URLMonitorPanel extends JPanel implements URLPingTask.URLUpdate {
    ScheduledThreadPoolExecutor executor;
    ScheduledFuture future;
    ...
    public URLMonitorPanel(String url, ScheduledThreadPoolExecutor se)
                          throws MalformedURLException {
        executor = se;
        ...
        stopButton.addActionListener(new ActionListener( ) {
            public void actionPerformed(ActionEvent ae) {
                future.cancel(true);
                startButton.setEnabled(true);
                stopButton.setEnabled(false);
            }
        });
        ...
    }

    private void makeTask( ) {
        task = new URLPingTask(url, this);
        future = executor.scheduleAtFixedRate(
                               task, 0L, 5L, TimeUnit.SECONDS);
    }

    public static void main(String[] args) throws Exception {
        ...
        ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(
                 (args.length + 1) / 2);
        for (int i = 0; i < args.length; i++) {
            c.add(new URLMonitorPanel(args[0], se));
        }
        ...
    }
}

The main enhancement that this change has bought us is the ability to specify a number of threads for the executor. We’ve chosen half as many threads as the machines we’re monitoring: in between the number of suboptimal choices we had previously. In this case, it would have been even more ideal for the task executor to be more flexible in its thread use.

The other case when using a scheduled executor makes sense is when you want to use the callable interface so that you can later check the status of the task. This is logical equivalent to using the join() method to tell if a thread is done.

We’ll extend our example slightly to see how this works. Let’s suppose we want our URL monitor to have a license; without a license, it runs in a demo mode for two minutes. In the absence of a valid license, we can set up a callable task that runs after a delay of two minutes. After that task has run, we know that the license period has expired.

We’ll have to poll the license task periodically to see whether it has finished. Normally, we don’t like polling because of its inefficiencies, but in this case, we have a perfect time to do it: because the status thread runs every five seconds, it can poll the license task without wasting much CPU time at all. Since in this case we don’t have to unnecessarily wake up a polling thread, we can afford the simple method call to handle the poll.

First, we need a simple task.

package javathreads.examples.ch11.example4;

class TimeoutTask implements Callable {
    public Integer call( ) throws IOException {
        return new Integer(0);
    }
}

As required, we’ve implemented the Callable interface. In this simple example, we don’t actually care about the return value: if the task has run, the license has expired. In a more complicated case, the license task might check with a license server and return a more interesting result. Checking with the license server might create an IOException, which is why we’ve declared that this task throws that exception.

Now we must add this to our monitor:

package javathreads.examples.ch11.example4;

public class URLMonitorPanel extends JPanel implements URLPingTask.URLUpdate {

    static Future<Integer> futureTaskResult;
    static volatile boolean done = false;
    ...

    private void checkLicense( ) {
        if (done) return;
        try {
            Integer I = futureTaskResult.get(0L, TimeUnit.MILLISECONDS);
            // If we got a result, we know that the license has expired
            JOptionPane.showMessageDialog(null,
                            "Evaluation time period has expired", "Expired",
                        JOptionPane.INFORMATION_MESSAGE);
            done = true;
        } catch (TimeoutException te) {
            // Task hasn't run; just coninue
        } catch (InterruptedException ie) {
            // Task was externally interrupted
        } catch (ExecutionException ee) {
            // Task threw IOException, which can be obtained like
            IOException ioe = (IOException) ee.getCause( );
            // Clean up after the exception
        }
    }

    public void isAlive(final boolean b) {
        try {
            SwingUtilities.invokeAndWait(new Runnable( ) {
                public void run( ) {
                    checkLicense( );
                    if (done) {
                        future.cancel(true);
                        startButton.setEnabled(false);
                        stopButton.setEnabled(false);
                        return;
                    }
                    status.setBackground(b ? Color.GREEN : Color.RED);
                    status.repaint( );
                }
            });
        } catch (Exception e) {}
    }

    public static void main(String[] args) throws Exception {
        ...
        TimeoutTask tt = new TimeoutTask( );
        futureTaskResult = se.schedule(tt, 120, TimeUnit.SECONDS);
        ...
    }
}

The checkLicense() method is called every time status is reported; it polls the timeout task. When the poll succeeds, the checkLicense() method sets a done flag so that other panels know that the license has expired (the done flag is static and shared among all panels). Alternately, we could let each panel poll the futureTaskResult object itself.

If you look carefully, you’ll notice that there’s no synchronization for the checkLicense() method and that it appears that the option pane might get displayed twice if two panels invoke that method at the same time. However, that’s not possible because the checkLicense() method is called via the invokeAndWait() method. That blocks the event-dispatching thread so we are already assured that only one thread at a time is executing the checkLicense() method.

In this chapter, we’ve looked at various ways in which tasks may be scheduled in the future. The simplest way to do this is to use the java.util.Timer class, which can run instances of a special class (the TimerTask class) at a point in the future, repeating the task if necessary. Each instance of a timer is a single thread; that thread can handle multiple tasks but long-running tasks may need their own thread (and consequently their own timer).

The javax.swing.Timer class is functionally similar, except that it ensures that tasks are run on the event-dispatching thread so that they may safely access Swing components. However, the javax.swing.Timer class has a fixed time schedule for all the tasks it runs; tasks that have different scheduling needs require different instances of the timer.

Finally, the ScheduledThreadPoolExecutor class provides a more flexible (but more complex) interface to task scheduling. Because it uses a thread pool, it can be more efficient when running a lot of tasks simultaneously. It also allows you to poll for task status or to use generic Runnable objects as your task.

The key benefit of task executors and timers is that they free you from having to worry about thread-related programming for your tasks: you simply feed the task to the timer or executor and let it worry about the necessary thread controls. This makes the code that you write that much simpler.