[ Team LiB ] Previous Section Next Section

4.5 Timers

Java 1.3 introduced the java.util.Timer class and the abstract java.util.TimerTask class. If you subclass TimerTask and implement its run( ) method, you can then use a Timer object to schedule invocations of that run( ) method at a specified time or at multiple times at a specified interval. One Timer object can schedule and invoke many TimerTask objects. Timer is quite useful, as it simplifies many programs that would otherwise have to create their own threads to provide the same functionality. Note that java.util.Timer is not at all the same as the Java 1.2 class javax.swing.Timer.

Examples Example 4-5 and Example 4-6 are simple implementations of the TimerTask and Timer classes that can be used prior to Java 1.3. They implement the same API as the Java 1.3 classes, except that they are in the je3.thread package instead of the java.util package. These implementations are not intended to be as robust as the official implementations in Java 1.3, but they are useful for simple tasks and are a good example of a nontrivial use of threads. Note in particular the use of wait( ) and notify( ) in Example 4-6. After studying these examples, you may be interested to compare them to the implementations that come with Java 1.3.[1]

[1] If you have the Java SDK™ from Sun, look in the src.jar archive that comes with it.

Example 4-5. TimerTask.java
package je3.thread;

/**
 * This class implements the same API as the Java 1.3 java.util.TimerTask.
 * Note that a TimerTask can only be scheduled on one Timer at a time, but
 * that this implementation does not enforce that constraint.
 **/
public abstract class TimerTask implements Runnable {
    boolean cancelled = false;    // Has it been cancelled?
    long nextTime = -1;           // When is it next scheduled?
    long period;                  // What is the execution interval?
    boolean fixedRate;            // Fixed-rate execution?

    protected TimerTask( ) {  }

    /**
     * Cancel the execution of the task.  Return true if it was actually
     * running, or false if it was already cancelled or never scheduled.
     **/
    public boolean cancel( ) {
        if (cancelled) return false;         // Already cancelled;
        cancelled = true;                    // Cancel it
        if (nextTime == -1) return false;    // Never scheduled;
        return true;
    }

    /**
     * When it the timer scheduled to execute? The run( ) method can use this
     * to see whether it was invoked when it was supposed to be 
     **/
    public long scheduledExecutionTime( ) { return nextTime; }

    /**
     * Subclasses must override this to provide that code that is to be run.
     * The Timer class will invoke this from its internal thread.
     **/
    public abstract void run( );

    // This method is used by Timer to tell the Task how it is scheduled.
    void schedule(long nextTime, long period, boolean fixedRate) {
        this.nextTime = nextTime;
        this.period = period;
        this.fixedRate = fixedRate;
    }

    // This will be called by Timer after Timer calls the run method.
    boolean reschedule( ) {
        if (period == 0 || cancelled) return false; // Don't run it again
        if (fixedRate) nextTime += period;
        else nextTime = System.currentTimeMillis( ) + period;
        return true;
    }
}
Example 4-6. Timer.java
package je3.thread;
import java.util.Date;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Comparator;

/**
 * This class is a simple implementation of the Java 1.3 java.util.Timer API
 **/
public class Timer {

    // This sorted set stores the tasks that this Timer is responsible for.
    // It uses a comparator to sort the tasks by scheduled execution time.
    SortedSet tasks = new TreeSet(new Comparator( ) {
            public int compare(Object a, Object b) {
                return (int)(((TimerTask)a).nextTime-((TimerTask)b).nextTime);
            }
            public boolean equals(Object o) { return this == o; }
        });


    // This is the thread the timer uses to execute the tasks.
    // The TimerThread class is defined below.
    TimerThread timer;

    /** This constructor creates a Timer that does not use a daemon thread */
    public Timer( ) { this(false); }

    /** The main constructor: the internal thread is a daemon if specified */
    public Timer(boolean isDaemon) {
        timer = new TimerThread(isDaemon);  // TimerThread is defined below
        timer.start( );                      // Start the thread running
    }

    /** Stop the timer thread, and discard all scheduled tasks */
    public void cancel( ) {
        synchronized(tasks) {     // Only one thread at a time!
            timer.pleaseStop( );   // Set a flag asking the thread to stop
            tasks.clear( );        // Discard all tasks
            tasks.notify( );       // Wake up the thread if it is in wait( ).
        }
    }

    /** Schedule a single execution after delay milliseconds */
    public void schedule(TimerTask task, long delay) {
        task.schedule(System.currentTimeMillis( ) + delay, 0, false);
        schedule(task);
    }

    /** Schedule a single execution at the specified time */
    public void schedule(TimerTask task, Date time) {
        task.schedule(time.getTime( ), 0, false);
        schedule(task);
    }

    /** Schedule a periodic execution starting at the specified time */
    public void schedule(TimerTask task, Date firstTime, long period) {
        task.schedule(firstTime.getTime( ), period, false);
        schedule(task);
    }

    /** Schedule a periodic execution starting after the specified delay */
    public void schedule(TimerTask task, long delay, long period) {
        task.schedule(System.currentTimeMillis( ) + delay, period, false);
        schedule(task);
    }

    /** 
     * Schedule a periodic execution starting after the specified delay.
     * Schedule fixed-rate executions period ms after the start of the last.
     * Instead of fixed-interval executions measured from the end of the last.
     **/
    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        task.schedule(System.currentTimeMillis( ) + delay, period, true);
        schedule(task);
    }

    /** Schedule a periodic execution starting after the specified time */
    public void scheduleAtFixedRate(TimerTask task, Date firstTime,
                                    long period)
    {
        task.schedule(firstTime.getTime( ), period, true);
        schedule(task);
    }


    // This internal method adds a task to the sorted set of tasks
    void schedule(TimerTask task) {
        synchronized(tasks) {  // Only one thread can modify tasks at a time!
            tasks.add(task);   // Add the task to the sorted set of tasks
            tasks.notify( );    // Wake up the thread if it is waiting
        }
    }

    /**
     * This inner class defines the thread that runs each of the tasks at their
     * scheduled times
     **/
    class TimerThread extends Thread {
        // This flag will be set true to tell the thread to stop running.
        // Note that it is declared volatile, which means that it may be 
        // changed asynchronously by another thread, so threads must always
        // read its current value, and not used a cached version.
        volatile boolean stopped = false;  

        // The constructor
        TimerThread(boolean isDaemon) { setDaemon(isDaemon); }

        // Ask the thread to stop by setting the flag above
        void pleaseStop( ) { stopped = true; }

        // This is the body of the thread
        public void run( ) {
            TimerTask readyToRun = null;  // Is there a task to run right now?

            // The thread loops until the stopped flag is set to true.
            while(!stopped) {
                // If there is a task that is ready to run, then run it!
                if (readyToRun != null) { 
                    if (readyToRun.cancelled) {  // If it was cancelled, skip.
                        readyToRun = null;
                        continue;
                    }
                    // Run the task.
                    readyToRun.run( );
                    // Ask it to reschedule itself, and if it wants to run 
                    // again, then insert it back into the set of tasks.
                    if (readyToRun.reschedule( ))
                        schedule(readyToRun);
                    // We've run it, so there is nothing to run now
                    readyToRun = null;
                    // Go back to top of the loop to see if we've been stopped
                    continue;
                }

                // Now acquire a lock on the set of tasks
                synchronized(tasks) {
                    long timeout;  // how many ms 'till the next execution?

                    if (tasks.isEmpty( )) {   // If there aren't any tasks
                        timeout = 0;  // Wait 'till notified of a new task
                    }
                    else {
                        // If there are scheduled tasks, then get the first one
                        // Since the set is sorted, this is the next one.
                        TimerTask t = (TimerTask) tasks.first( );
                        // How long 'till it is next run?
                        timeout = t.nextTime - System.currentTimeMillis( );
                        // Check whether it needs to run now
                        if (timeout <= 0) {
                            readyToRun = t;  // Save it as ready to run
                            tasks.remove(t); // Remove it from the set
                            // Break out of the synchronized section before
                            // we run the task
                            continue;
                        }
                    }

                    // If we get here, there is nothing ready to run now,
                    // so wait for time to run out, or wait 'till notify( ) is
                    // called when something new is added to the set of tasks.
                    try { tasks.wait(timeout); }
                    catch (InterruptedException e) {  }

                    // When we wake up, go back up to the top of the while loop
                }
            }
        }
    }
    
    /** This inner class defines a test program */
    public static class Test {
        public static void main(String[  ] args) {
            final TimerTask t1 = new TimerTask( ) { // Task 1: print "boom"
                    public void run( ) { System.out.println("boom"); }
                };
            final TimerTask t2 = new TimerTask( ) { // Task 2: print "BOOM"
                    public void run( ) { System.out.println("\tBOOM"); }
                };
            final TimerTask t3 = new TimerTask( ) { // Task 3: cancel the tasks
                    public void run( ) { t1.cancel( ); t2.cancel( ); }
                };
            
            // Create a timer, and schedule some tasks
            final Timer timer = new Timer( );
            timer.schedule(t1, 0, 500);     // boom every .5sec starting now
            timer.schedule(t2, 2000, 2000); // BOOM every 2s, starting in 2s
            timer.schedule(t3, 5000);       // Stop them after 5 seconds

            // Schedule a final task: starting in 5 seconds, count
            // down from 5, then destroy the timer, which, since it is
            // the only remaining thread, will cause the program to exit.
            timer.scheduleAtFixedRate(new TimerTask( ) {
                    public int times = 5;
                    public void run( ) {
                        System.out.println(times--);
                        if (times == 0) timer.cancel( );
                    }
                }, 
                                      5000,500);
        }
    }
}
    [ Team LiB ] Previous Section Next Section