Timers

TestTask

A timer task is a background thread that is scheduled to run at a specific time by a timer object. Here's a possible implementation:

abstract class TimerTask implements Runnable, Comparable {
   long period; // restart every period msecs
   long set; // next scheduled execution time
   public long scheduledExecutionTime() { return set; }
   public void cancel() { period = 0; }
   public int compareTo(Object other) {
      return (int)(set - ((TimerTask)other).set);
   }
}

Note that TimerTask is abstract because it doesn't implement run.

A timer is a thread that maintains a priority queue (implemented as a binary heap) of timer tasks prioritized by their scheduled execution times:

public class Timer extends Thread {
   private PriorityQueue queue = new BinaryHeap();
   public Timer() { start(); } // create then start

The run method perpetually:

1. deletes the next task
2. waits for its scheduled execution time
3. runs it
4. reschedules it if period > 0

Here's a possible implementation:

   public void run() {
      TimerTask next;
      while(true) {
         try {
            synchronized(queue) {
               next = (TimerTask)queue.deleteMin();
            }
            long now = System.currentTimeMillis();
            long set = next.scheduledExecutionTime();
            long delay = set - now;
            delay = (0<delay)?delay:0;
            synchronized(next) {
               next.wait((int)delay);
               Thread thread = new Thread(next);
               thread.start();
               if (0 < next.period) schedule(next, 0, next.period);
            }
         } catch(Exception e) { /* try again */ }
      }
   }

Scheduling a task means computing its next execution time and inserting it back into the queue:

   public void schedule(TimerTask task, long delay) {
      try {
         synchronized(queue) {
            task.set = System.currentTimeMillis() + delay;
            queue.insert(task);
         }
      } catch(Exception e) {  }
   }

Scheduling a repeating task means specifying a period:

   public void schedule(
      TimerTask task, long delay, long period) {
      try {
         task.period = period;
         task.set = System.currentTimeMillis() + delay + period;
         queue.insert(task);
      } catch(Exception e) {  }
   }
   // etc.
} // Timer

Experiments

Running a test task prints its scheduled execution time (SET). After five runs it cancels itself:

class TestTask extends TimerTask {
   private int id;
   private int count = 0;
   public String toString() { return "task[" + id + "]"; }
   public TestTask(int i) { id = i; }
   public void run() {
      long time = scheduledExecutionTime();
      System.out.println(toString() + " SET = " + time % 100000);
      if (5 <= ++count) {
         System.out.println(toString() + " cancelling itself");
         cancel();
      }
   }
}

Our main method creates a timer and an array of test tasks:

public static void main(String[] args) {
   Timer timer = new Timer(); // create a new timer
   // create a bunch ot test tasks:
   TimerTask[] task = new TimerTask[10];
   for(int i = 0; i < 10; i++) task[i] = new TestTask(i);
   // etc.
}

Experiment 1

   // after 1000, do task[0] once
   timer.schedule(task[0], 1000);
   // after 1000, repeat task[1] every 500
   timer.schedule(task[1], 1000, 500);
   Date then = new Date(System.currentTimeMillis() + 2000);
   // after 5000, do task[0] once
   timer.schedule(task[2], then);
   timer.scheduleAtFixedRate(task[3], 0, 100);

Results

task[3] SET = 12583
task[3] SET = 12683
task[3] SET = 12783
task[3] SET = 12883
task[3] SET = 12983
task[3] cancelling itself
task[1] SET = 13585
task[0] SET = 13583
task[1] SET = 14086
task[2] SET = 14583
task[1] SET = 14586
task[1] SET = 15087
task[1] SET = 15588
task[1] cancelling itself

Experiment 2

timer.scheduleAtFixedRate(task[3], 0, 100);
try {
   System.out.println("timer sleeping for 5000");
   Thread.sleep(5000);
   System.out.println("timer waking up");
} catch(InterruptedException ie) {
   System.err.println(ie.getMessage());
   System.exit(1);
}

// after 500, do task[4] once
timer.schedule(task[4], 500);

try {
   System.out.println("timer sleeping for 5000");
   Thread.sleep(5000);
   System.out.println("timer waking up and cancelling itself");
   timer.cancel();
} catch(InterruptedException ie) {
   System.err.println(ie.getMessage());
   System.exit(1);
}

Results

timer sleeping for 5000
task[3] SET = 64826
task[3] SET = 64926
task[3] SET = 65026
task[3] SET = 65126
task[3] SET = 65226
task[3] cancelling itself
timer waking up
timer sleeping for 5000
task[4] SET = 70323
timer waking up and cancelling itself