Mercurial > hg > blitz_condensed
diff src/EDU/oswego/cs/dl/util/concurrent/ClockDaemon.java @ 0:3dc0c5604566
Initial checkin of blitz 2.0 fcs - no installer yet.
author | Dan Creswell <dan.creswell@gmail.com> |
---|---|
date | Sat, 21 Mar 2009 11:00:06 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EDU/oswego/cs/dl/util/concurrent/ClockDaemon.java Sat Mar 21 11:00:06 2009 +0000 @@ -0,0 +1,387 @@ +/* + File: ClockDaemon.java + + Originally written by Doug Lea and released into the public domain. + This may be used for any purposes whatsoever without acknowledgment. + Thanks for the assistance and support of Sun Microsystems Labs, + and everyone contributing, testing, and using this code. + + History: + Date Who What + 29Aug1998 dl created initial public version + 17dec1998 dl null out thread after shutdown +*/ + +package EDU.oswego.cs.dl.util.concurrent; +import java.util.Comparator; +import java.util.Date; + +/** + * A general-purpose time-based daemon, vaguely similar in functionality + * to common system-level utilities such as <code>at</code> + * (and the associated crond) in Unix. + * Objects of this class maintain a single thread and a task queue + * that may be used to execute Runnable commands in any of three modes -- + * absolute (run at a given time), relative (run after a given delay), + * and periodic (cyclically run with a given delay). + * <p> + * All commands are executed by the single background thread. + * The thread is not actually started until the first + * request is encountered. Also, if the + * thread is stopped for any reason, one is started upon encountering + * the next request, or <code>restart()</code> is invoked. + * <p> + * If you would instead like commands run in their own threads, you can + * use as arguments Runnable commands that start their own threads + * (or perhaps wrap within ThreadedExecutors). + * <p> + * You can also use multiple + * daemon objects, each using a different background thread. However, + * one of the reasons for using a time daemon is to pool together + * processing of infrequent tasks using a single background thread. + * <p> + * Background threads are created using a ThreadFactory. The + * default factory does <em>not</em> + * automatically <code>setDaemon</code> status. + * <p> + * The class uses Java timed waits for scheduling. These can vary + * in precision across platforms, and provide no real-time guarantees + * about meeting deadlines. + * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>] + **/ + +public class ClockDaemon extends ThreadFactoryUser { + + + /** tasks are maintained in a standard priority queue **/ + protected final Heap heap_ = new Heap(DefaultChannelCapacity.get()); + + + protected static class TaskNode implements Comparable { + final Runnable command; // The command to run + final long period; // The cycle period, or -1 if not periodic + private long timeToRun_; // The time to run command + + // Cancellation does not immediately remove node, it just + // sets up lazy deletion bit, so is thrown away when next + // encountered in run loop + + private boolean cancelled_ = false; + + // Access to cancellation status and and run time needs sync + // since they can be written and read in different threads + + synchronized void setCancelled() { cancelled_ = true; } + synchronized boolean getCancelled() { return cancelled_; } + + synchronized void setTimeToRun(long w) { timeToRun_ = w; } + synchronized long getTimeToRun() { return timeToRun_; } + + + public int compareTo(Object other) { + long a = getTimeToRun(); + long b = ((TaskNode)(other)).getTimeToRun(); + return (a < b)? -1 : ((a == b)? 0 : 1); + } + + TaskNode(long w, Runnable c, long p) { + timeToRun_ = w; command = c; period = p; + } + + TaskNode(long w, Runnable c) { this(w, c, -1); } + } + + + /** + * Execute the given command at the given time. + * @param date -- the absolute time to run the command, expressed + * as a java.util.Date. + * @param command -- the command to run at the given time. + * @return taskID -- an opaque reference that can be used to cancel execution request + **/ + public Object executeAt(Date date, Runnable command) { + TaskNode task = new TaskNode(date.getTime(), command); + heap_.insert(task); + restart(); + return task; + } + + /** + * Excecute the given command after waiting for the given delay. + * <p> + * <b>Sample Usage.</b> + * You can use a ClockDaemon to arrange timeout callbacks to break out + * of stuck IO. For example (code sketch): + * <pre> + * class X { ... + * + * ClockDaemon timer = ... + * Thread readerThread; + * FileInputStream datafile; + * + * void startReadThread() { + * datafile = new FileInputStream("data", ...); + * + * readerThread = new Thread(new Runnable() { + * public void run() { + * for(;;) { + * // try to gracefully exit before blocking + * if (Thread.currentThread().isInterrupted()) { + * quietlyWrapUpAndReturn(); + * } + * else { + * try { + * int c = datafile.read(); + * if (c == -1) break; + * else process(c); + * } + * catch (IOException ex) { + * cleanup(); + * return; + * } + * } + * } }; + * + * readerThread.start(); + * + * // establish callback to cancel after 60 seconds + * timer.executeAfterDelay(60000, new Runnable() { + * readerThread.interrupt(); // try to interrupt thread + * datafile.close(); // force thread to lose its input file + * }); + * } + * } + * </pre> + * @param millisecondsToDelay -- the number of milliseconds + * from now to run the command. + * @param command -- the command to run after the delay. + * @return taskID -- an opaque reference that can be used to cancel execution request + **/ + public Object executeAfterDelay(long millisecondsToDelay, Runnable command) { + long runtime = System.currentTimeMillis() + millisecondsToDelay; + TaskNode task = new TaskNode(runtime, command); + heap_.insert(task); + restart(); + return task; + } + + /** + * Execute the given command every <code>period</code> milliseconds. + * If <code>startNow</code> is true, execution begins immediately, + * otherwise, it begins after the first <code>period</code> delay. + * <p> + * <b>Sample Usage</b>. Here is one way + * to update Swing components acting as progress indicators for + * long-running actions. + * <pre> + * class X { + * JLabel statusLabel = ...; + * + * int percentComplete = 0; + * synchronized int getPercentComplete() { return percentComplete; } + * synchronized void setPercentComplete(int p) { percentComplete = p; } + * + * ClockDaemon cd = ...; + * + * void startWorking() { + * Runnable showPct = new Runnable() { + * public void run() { + * SwingUtilities.invokeLater(new Runnable() { + * public void run() { + * statusLabel.setText(getPercentComplete() + "%"); + * } + * } + * } + * }; + * + * final Object updater = cd.executePeriodically(500, showPct, true); + * + * Runnable action = new Runnable() { + * public void run() { + * for (int i = 0; i < 100; ++i) { + * work(); + * setPercentComplete(i); + * } + * cd.cancel(updater); + * } + * }; + * + * new Thread(action).start(); + * } + * } + * </pre> + * @param period -- the period, in milliseconds. Periods are + * measured from start-of-task to the next start-of-task. It is + * generally a bad idea to use a period that is shorter than + * the expected task duration. + * @param command -- the command to run at each cycle + * @param startNow -- true if the cycle should start with execution + * of the task now. Otherwise, the cycle starts with a delay of + * <code>period</code> milliseconds. + * @exception IllegalArgumentException if period less than or equal to zero. + * @return taskID -- an opaque reference that can be used to cancel execution request + **/ + public Object executePeriodically(long period, + Runnable command, + boolean startNow) { + + if (period <= 0) throw new IllegalArgumentException(); + + long firstTime = System.currentTimeMillis(); + if (!startNow) firstTime += period; + + TaskNode task = new TaskNode(firstTime, command, period); + heap_.insert(task); + restart(); + return task; + } + + /** + * Cancel a scheduled task that has not yet been run. + * The task will be cancelled + * upon the <em>next</em> opportunity to run it. This has no effect if + * this is a one-shot task that has already executed. + * Also, if an execution is in progress, it will complete normally. + * (It may however be interrupted via getThread().interrupt()). + * But if it is a periodic task, future iterations are cancelled. + * @param taskID -- a task reference returned by one of + * the execute commands + * @exception ClassCastException if the taskID argument is not + * of the type returned by an execute command. + **/ + public static void cancel(Object taskID) { + ((TaskNode)taskID).setCancelled(); + } + + + /** The thread used to process commands **/ + protected Thread thread_; + + + /** + * Return the thread being used to process commands, or + * null if there is no such thread. You can use this + * to invoke any special methods on the thread, for + * example, to interrupt it. + **/ + public synchronized Thread getThread() { + return thread_; + } + + /** set thread_ to null to indicate termination **/ + protected synchronized void clearThread() { + thread_ = null; + } + + /** + * Start (or restart) a thread to process commands, or wake + * up an existing thread if one is already running. This + * method can be invoked if the background thread crashed + * due to an unrecoverable exception in an executed command. + **/ + + public synchronized void restart() { + if (thread_ == null) { + thread_ = threadFactory_.newThread(runLoop_); + thread_.start(); + } + else + notify(); + } + + + /** + * Cancel all tasks and interrupt the background thread executing + * the current task, if any. + * A new background thread will be started if new execution + * requests are encountered. If the currently executing task + * does not repsond to interrupts, the current thread may persist, even + * if a new thread is started via restart(). + **/ + public synchronized void shutDown() { + heap_.clear(); + if (thread_ != null) + thread_.interrupt(); + thread_ = null; + } + + /** Return the next task to execute, or null if thread is interrupted **/ + protected synchronized TaskNode nextTask() { + + // Note: This code assumes that there is only one run loop thread + + try { + while (!Thread.interrupted()) { + + // Using peek simplifies dealing with spurious wakeups + + TaskNode task = (TaskNode)(heap_.peek()); + + if (task == null) { + wait(); + } + else { + long now = System.currentTimeMillis(); + long when = task.getTimeToRun(); + + if (when > now) { // false alarm wakeup + wait(when - now); + } + else { + task = (TaskNode)(heap_.extract()); + + if (!task.getCancelled()) { // Skip if cancelled by + + if (task.period > 0) { // If periodic, requeue + task.setTimeToRun(now + task.period); + heap_.insert(task); + } + + return task; + } + } + } + } + } + catch (InterruptedException ex) { } // fall through + + return null; // on interrupt + } + + /** + * The runloop is isolated in its own Runnable class + * just so that the main + * class need not implement Runnable, which would + * allow others to directly invoke run, which is not supported. + **/ + + protected class RunLoop implements Runnable { + public void run() { + try { + for (;;) { + TaskNode task = nextTask(); + if (task != null) + task.command.run(); + else + break; + } + } + finally { + clearThread(); + } + } + } + + protected final RunLoop runLoop_; + + /** + * Create a new ClockDaemon + **/ + + public ClockDaemon() { + runLoop_ = new RunLoop(); + } + + + +}