001: /*
002: File: ClockDaemon.java
003:
004: Originally written by Doug Lea and released into the public domain.
005: This may be used for any purposes whatsoever without acknowledgment.
006: Thanks for the assistance and support of Sun Microsystems Labs,
007: and everyone contributing, testing, and using this code.
008:
009: History:
010: Date Who What
011: 29Aug1998 dl created initial public version
012: 17dec1998 dl null out thread after shutdown
013: */
014:
015: package EDU.oswego.cs.dl.util.concurrent;
016:
017: import java.util.Comparator;
018: import java.util.Date;
019:
020: /**
021: * A general-purpose time-based daemon, vaguely similar in functionality
022: * to common system-level utilities such as <code>at</code>
023: * (and the associated crond) in Unix.
024: * Objects of this class maintain a single thread and a task queue
025: * that may be used to execute Runnable commands in any of three modes --
026: * absolute (run at a given time), relative (run after a given delay),
027: * and periodic (cyclically run with a given delay).
028: * <p>
029: * All commands are executed by the single background thread.
030: * The thread is not actually started until the first
031: * request is encountered. Also, if the
032: * thread is stopped for any reason, one is started upon encountering
033: * the next request, or <code>restart()</code> is invoked.
034: * <p>
035: * If you would instead like commands run in their own threads, you can
036: * use as arguments Runnable commands that start their own threads
037: * (or perhaps wrap within ThreadedExecutors).
038: * <p>
039: * You can also use multiple
040: * daemon objects, each using a different background thread. However,
041: * one of the reasons for using a time daemon is to pool together
042: * processing of infrequent tasks using a single background thread.
043: * <p>
044: * Background threads are created using a ThreadFactory. The
045: * default factory does <em>not</em>
046: * automatically <code>setDaemon</code> status.
047: * <p>
048: * The class uses Java timed waits for scheduling. These can vary
049: * in precision across platforms, and provide no real-time guarantees
050: * about meeting deadlines.
051: * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
052: **/
053:
054: public class ClockDaemon extends ThreadFactoryUser {
055:
056: /** tasks are maintained in a standard priority queue **/
057: protected final Heap heap_ = new Heap(DefaultChannelCapacity.get());
058:
059: protected static class TaskNode implements Comparable {
060: final Runnable command; // The command to run
061: final long period; // The cycle period, or -1 if not periodic
062: private long timeToRun_; // The time to run command
063:
064: // Cancellation does not immediately remove node, it just
065: // sets up lazy deletion bit, so is thrown away when next
066: // encountered in run loop
067:
068: private boolean cancelled_ = false;
069:
070: // Access to cancellation status and and run time needs sync
071: // since they can be written and read in different threads
072:
073: synchronized void setCancelled() {
074: cancelled_ = true;
075: }
076:
077: synchronized boolean getCancelled() {
078: return cancelled_;
079: }
080:
081: synchronized void setTimeToRun(long w) {
082: timeToRun_ = w;
083: }
084:
085: synchronized long getTimeToRun() {
086: return timeToRun_;
087: }
088:
089: public int compareTo(Object other) {
090: long a = getTimeToRun();
091: long b = ((TaskNode) (other)).getTimeToRun();
092: return (a < b) ? -1 : ((a == b) ? 0 : 1);
093: }
094:
095: TaskNode(long w, Runnable c, long p) {
096: timeToRun_ = w;
097: command = c;
098: period = p;
099: }
100:
101: TaskNode(long w, Runnable c) {
102: this (w, c, -1);
103: }
104: }
105:
106: /**
107: * Execute the given command at the given time.
108: * @param date -- the absolute time to run the command, expressed
109: * as a java.util.Date.
110: * @param command -- the command to run at the given time.
111: * @return taskID -- an opaque reference that can be used to cancel execution request
112: **/
113: public Object executeAt(Date date, Runnable command) {
114: TaskNode task = new TaskNode(date.getTime(), command);
115: heap_.insert(task);
116: restart();
117: return task;
118: }
119:
120: /**
121: * Excecute the given command after waiting for the given delay.
122: * <p>
123: * <b>Sample Usage.</b>
124: * You can use a ClockDaemon to arrange timeout callbacks to break out
125: * of stuck IO. For example (code sketch):
126: * <pre>
127: * class X { ...
128: *
129: * ClockDaemon timer = ...
130: * Thread readerThread;
131: * FileInputStream datafile;
132: *
133: * void startReadThread() {
134: * datafile = new FileInputStream("data", ...);
135: *
136: * readerThread = new Thread(new Runnable() {
137: * public void run() {
138: * for(;;) {
139: * // try to gracefully exit before blocking
140: * if (Thread.currentThread().isInterrupted()) {
141: * quietlyWrapUpAndReturn();
142: * }
143: * else {
144: * try {
145: * int c = datafile.read();
146: * if (c == -1) break;
147: * else process(c);
148: * }
149: * catch (IOException ex) {
150: * cleanup();
151: * return;
152: * }
153: * }
154: * } };
155: *
156: * readerThread.start();
157: *
158: * // establish callback to cancel after 60 seconds
159: * timer.executeAfterDelay(60000, new Runnable() {
160: * readerThread.interrupt(); // try to interrupt thread
161: * datafile.close(); // force thread to lose its input file
162: * });
163: * }
164: * }
165: * </pre>
166: * @param millisecondsToDelay -- the number of milliseconds
167: * from now to run the command.
168: * @param command -- the command to run after the delay.
169: * @return taskID -- an opaque reference that can be used to cancel execution request
170: **/
171: public Object executeAfterDelay(long millisecondsToDelay,
172: Runnable command) {
173: long runtime = System.currentTimeMillis() + millisecondsToDelay;
174: TaskNode task = new TaskNode(runtime, command);
175: heap_.insert(task);
176: restart();
177: return task;
178: }
179:
180: /**
181: * Execute the given command every <code>period</code> milliseconds.
182: * If <code>startNow</code> is true, execution begins immediately,
183: * otherwise, it begins after the first <code>period</code> delay.
184: * <p>
185: * <b>Sample Usage</b>. Here is one way
186: * to update Swing components acting as progress indicators for
187: * long-running actions.
188: * <pre>
189: * class X {
190: * JLabel statusLabel = ...;
191: *
192: * int percentComplete = 0;
193: * synchronized int getPercentComplete() { return percentComplete; }
194: * synchronized void setPercentComplete(int p) { percentComplete = p; }
195: *
196: * ClockDaemon cd = ...;
197: *
198: * void startWorking() {
199: * Runnable showPct = new Runnable() {
200: * public void run() {
201: * SwingUtilities.invokeLater(new Runnable() {
202: * public void run() {
203: * statusLabel.setText(getPercentComplete() + "%");
204: * }
205: * }
206: * }
207: * };
208: *
209: * final Object updater = cd.executePeriodically(500, showPct, true);
210: *
211: * Runnable action = new Runnable() {
212: * public void run() {
213: * for (int i = 0; i < 100; ++i) {
214: * work();
215: * setPercentComplete(i);
216: * }
217: * cd.cancel(updater);
218: * }
219: * };
220: *
221: * new Thread(action).start();
222: * }
223: * }
224: * </pre>
225: * @param period -- the period, in milliseconds. Periods are
226: * measured from start-of-task to the next start-of-task. It is
227: * generally a bad idea to use a period that is shorter than
228: * the expected task duration.
229: * @param command -- the command to run at each cycle
230: * @param startNow -- true if the cycle should start with execution
231: * of the task now. Otherwise, the cycle starts with a delay of
232: * <code>period</code> milliseconds.
233: * @exception IllegalArgumentException if period less than or equal to zero.
234: * @return taskID -- an opaque reference that can be used to cancel execution request
235: **/
236: public Object executePeriodically(long period, Runnable command,
237: boolean startNow) {
238:
239: if (period <= 0)
240: throw new IllegalArgumentException();
241:
242: long firstTime = System.currentTimeMillis();
243: if (!startNow)
244: firstTime += period;
245:
246: TaskNode task = new TaskNode(firstTime, command, period);
247: heap_.insert(task);
248: restart();
249: return task;
250: }
251:
252: /**
253: * Cancel a scheduled task that has not yet been run.
254: * The task will be cancelled
255: * upon the <em>next</em> opportunity to run it. This has no effect if
256: * this is a one-shot task that has already executed.
257: * Also, if an execution is in progress, it will complete normally.
258: * (It may however be interrupted via getThread().interrupt()).
259: * But if it is a periodic task, future iterations are cancelled.
260: * @param taskID -- a task reference returned by one of
261: * the execute commands
262: * @exception ClassCastException if the taskID argument is not
263: * of the type returned by an execute command.
264: **/
265: public static void cancel(Object taskID) {
266: ((TaskNode) taskID).setCancelled();
267: }
268:
269: /** The thread used to process commands **/
270: protected Thread thread_;
271:
272: /**
273: * Return the thread being used to process commands, or
274: * null if there is no such thread. You can use this
275: * to invoke any special methods on the thread, for
276: * example, to interrupt it.
277: **/
278: public synchronized Thread getThread() {
279: return thread_;
280: }
281:
282: /** set thread_ to null to indicate termination **/
283: protected synchronized void clearThread() {
284: thread_ = null;
285: }
286:
287: /**
288: * Start (or restart) a thread to process commands, or wake
289: * up an existing thread if one is already running. This
290: * method can be invoked if the background thread crashed
291: * due to an unrecoverable exception in an executed command.
292: **/
293:
294: public synchronized void restart() {
295: if (thread_ == null) {
296: thread_ = threadFactory_.newThread(runLoop_);
297: thread_.start();
298: } else
299: notify();
300: }
301:
302: /**
303: * Cancel all tasks and interrupt the background thread executing
304: * the current task, if any.
305: * A new background thread will be started if new execution
306: * requests are encountered. If the currently executing task
307: * does not repsond to interrupts, the current thread may persist, even
308: * if a new thread is started via restart().
309: **/
310: public synchronized void shutDown() {
311: heap_.clear();
312: if (thread_ != null)
313: thread_.interrupt();
314: thread_ = null;
315: }
316:
317: /** Return the next task to execute, or null if thread is interrupted **/
318: protected synchronized TaskNode nextTask() {
319:
320: // Note: This code assumes that there is only one run loop thread
321:
322: try {
323: while (!Thread.interrupted()) {
324:
325: // Using peek simplifies dealing with spurious wakeups
326:
327: TaskNode task = (TaskNode) (heap_.peek());
328:
329: if (task == null) {
330: wait();
331: } else {
332: long now = System.currentTimeMillis();
333: long when = task.getTimeToRun();
334:
335: if (when > now) { // false alarm wakeup
336: wait(when - now);
337: } else {
338: task = (TaskNode) (heap_.extract());
339:
340: if (!task.getCancelled()) { // Skip if cancelled by
341:
342: if (task.period > 0) { // If periodic, requeue
343: task.setTimeToRun(now + task.period);
344: heap_.insert(task);
345: }
346:
347: return task;
348: }
349: }
350: }
351: }
352: } catch (InterruptedException ex) {
353: } // fall through
354:
355: return null; // on interrupt
356: }
357:
358: /**
359: * The runloop is isolated in its own Runnable class
360: * just so that the main
361: * class need not implement Runnable, which would
362: * allow others to directly invoke run, which is not supported.
363: **/
364:
365: protected class RunLoop implements Runnable {
366: public void run() {
367: try {
368: for (;;) {
369: TaskNode task = nextTask();
370: if (task != null)
371: task.command.run();
372: else
373: break;
374: }
375: } finally {
376: clearThread();
377: }
378: }
379: }
380:
381: protected final RunLoop runLoop_;
382:
383: /**
384: * Create a new ClockDaemon
385: **/
386:
387: public ClockDaemon() {
388: runLoop_ = new RunLoop();
389: }
390:
391: }
|