/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
import java.util.Comparator;
import java.util.Date;
import java.util.SortedSet;
import java.util.TreeSet;
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;
}
}
/**
* 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 create 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 is 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);
}
}
}
|