--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/misc/Timer.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,647 @@
+/*
+ * Copyright 1995 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.misc;
+
+/**
+ A Timer object is used by algorithms that require timed events.
+ For example, in an animation loop, a timer would help in
+ determining when to change frames.
+
+ A timer has an interval which determines when it "ticks";
+ that is, a timer delays for the specified interval and then
+ it calls the owner's tick() method.
+
+ Here's an example of creating a timer with a 5 sec interval:
+
+ <pre>
+ class Main implements Timeable {
+ public void tick(Timer timer) {
+ System.out.println("tick");
+ }
+ public static void main(String args[]) {
+ (new Timer(this, 5000)).cont();
+ }
+ }
+ </pre>
+
+ A timer can be stopped, continued, or reset at any time.
+ A timer's state is not stopped while it's calling the
+ owner's tick() method.
+
+ A timer can be regular or irregular. If in regular mode,
+ a timer ticks at the specified interval, regardless of
+ how long the owner's tick() method takes. While the timer
+ is running, no ticks are ever discarded. That means that if
+ the owner's tick() method takes longer than the interval,
+ the ticks that would have occurred are delivered immediately.
+
+ In irregular mode, a timer starts delaying for exactly
+ the specified interval only after the tick() method returns.
+
+ Synchronization issues: do not hold the timer's monitor
+ while calling any of the Timer operations below otherwise
+ the Timer class will deadlock.
+
+ @author Patrick Chan
+*/
+
+/*
+ Synchronization issues: there are two data structures that
+ require locking. A Timer object and the Timer queue
+ (described in the TimerThread class). To avoid deadlock,
+ the timer queue monitor is always acquired before the timer
+ object's monitor. However, the timer queue monitor is acquired
+ only if the timer operation will make use of the timer
+ queue, e.g. stop().
+
+ The class monitor on the class TimerThread severs as the monitor
+ to the timer queue.
+
+ Possible feature: perhaps a timer should have an associated
+ thread priority. The thread that makes the callback temporarily
+ takes on that priority before calling the owner's tick() method.
+*/
+
+public class Timer {
+ /**
+ * This is the owner of the timer. Its tick method is
+ * called when the timer ticks.
+ */
+ public Timeable owner;
+
+ /*
+ * This is the interval of time in ms.
+ */
+ long interval;
+
+ /*
+ * This variable is used for two different purposes.
+ * This is done in order to save space.
+ * If 'stopped' is true, this variable holds the time
+ * that the timer was stopped; otherwise, this variable
+ * is used by the TimerThread to determine when the timer
+ * should tick.
+ */
+ long sleepUntil;
+
+ /*
+ * This is the time remaining before the timer ticks. It
+ * is only valid if 'stopped' is true. If the timer is
+ * continued, the next tick will happen remaingTime
+ * milliseconds later.
+ */
+ long remainingTime;
+
+ /*
+ * True iff the timer is in regular mode.
+ */
+ boolean regular;
+
+ /*
+ * True iff the timer has been stopped.
+ */
+ boolean stopped;
+
+ /* **************************************************************
+ * Timer queue-related variables
+ * ************************************************************** */
+
+ /*
+ * A link to another timer object. This is used while the
+ * timer object is enqueued in the timer queue.
+ */
+ Timer next;
+
+ /* **************************************************************
+ * Timer methods
+ * ************************************************************** */
+
+ /*
+ * This variable holds a handle to the TimerThread class for
+ * the purpose of getting at the class monitor. The reason
+ * why Class.forName("TimerThread") is not used is because it
+ * doesn't appear to work when loaded via a net class loader.
+ */
+ static TimerThread timerThread = null;
+
+ /**
+ * Creates a timer object that is owned by 'owner' and
+ * with the interval 'interval' milliseconds. The new timer
+ * object is stopped and is regular. getRemainingTime()
+ * return 'interval' at this point. getStopTime() returns
+ * the time this object was created.
+ * @param owner owner of the timer object
+ * @param interval interval of the timer in milliseconds
+ */
+ public Timer(Timeable owner, long interval) {
+ this.owner = owner;
+ this.interval = interval;
+ remainingTime = interval;
+ regular = true;
+ sleepUntil = System.currentTimeMillis();
+ stopped = true;
+ synchronized (getClass()) {
+ if (timerThread == null) {
+ timerThread = new TimerThread();
+ }
+ }
+ }
+
+ /**
+ * Returns true if this timer is stopped.
+ */
+ public synchronized boolean isStopped() {
+ return stopped;
+ }
+
+ /**
+ * Stops the timer. The amount of time the timer has already
+ * delayed is saved so if the timer is continued, it will only
+ * delay for the amount of time remaining.
+ * Note that even after stopping a timer, one more tick may
+ * still occur.
+ * This method is MT-safe; i.e. it is synchronized but for
+ * implementation reasons, the synchronized modifier cannot
+ * be included in the method declaration.
+ */
+ public void stop() {
+ long now = System.currentTimeMillis();
+
+ synchronized (timerThread) {
+ synchronized (this) {
+ if (!stopped) {
+ TimerThread.dequeue(this);
+ remainingTime = Math.max(0, sleepUntil - now);
+ sleepUntil = now; // stop time
+ stopped = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Continue the timer. The next tick will come at getRemainingTime()
+ * milliseconds later. If the timer is not stopped, this
+ * call will be a no-op.
+ * This method is MT-safe; i.e. it is synchronized but for
+ * implementation reasons, the synchronized modifier cannot
+ * be included in the method declaration.
+ */
+ public void cont() {
+ synchronized (timerThread) {
+ synchronized (this) {
+ if (stopped) {
+ // The TimerTickThread avoids requeuing the
+ // timer only if the sleepUntil value has changed.
+ // The following guarantees that the sleepUntil
+ // value will be different; without this guarantee,
+ // it's theoretically possible for the timer to be
+ // inserted twice.
+ sleepUntil = Math.max(sleepUntil + 1,
+ System.currentTimeMillis() + remainingTime);
+ TimerThread.enqueue(this);
+ stopped = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Resets the timer's remaining time to the timer's interval.
+ * If the timer's running state is not altered.
+ */
+ public void reset() {
+ synchronized (timerThread) {
+ synchronized (this) {
+ setRemainingTime(interval);
+ }
+ }
+ }
+
+ /**
+ * Returns the time at which the timer was last stopped. The
+ * return value is valid only if the timer is stopped.
+ */
+ public synchronized long getStopTime() {
+ return sleepUntil;
+ }
+
+ /**
+ * Returns the timer's interval.
+ */
+ public synchronized long getInterval() {
+ return interval;
+ }
+
+ /**
+ * Changes the timer's interval. The new interval setting
+ * does not take effect until after the next tick.
+ * This method does not alter the remaining time or the
+ * running state of the timer.
+ * @param interval new interval of the timer in milliseconds
+ */
+ public synchronized void setInterval(long interval) {
+ this.interval = interval;
+ }
+
+ /**
+ * Returns the remaining time before the timer's next tick.
+ * The return value is valid only if timer is stopped.
+ */
+ public synchronized long getRemainingTime() {
+ return remainingTime;
+ }
+
+ /**
+ * Sets the remaining time before the timer's next tick.
+ * This method does not alter the timer's running state.
+ * This method is MT-safe; i.e. it is synchronized but for
+ * implementation reasons, the synchronized modifier cannot
+ * be included in the method declaration.
+ * @param time new remaining time in milliseconds.
+ */
+ public void setRemainingTime(long time) {
+ synchronized (timerThread) {
+ synchronized (this) {
+ if (stopped) {
+ remainingTime = time;
+ } else {
+ stop();
+ remainingTime = time;
+ cont();
+ }
+ }
+ }
+ }
+
+ /**
+ * In regular mode, a timer ticks at the specified interval,
+ * regardless of how long the owner's tick() method takes.
+ * While the timer is running, no ticks are ever discarded.
+ * That means that if the owner's tick() method takes longer
+ * than the interval, the ticks that would have occurred are
+ * delivered immediately.
+ *
+ * In irregular mode, a timer starts delaying for exactly
+ * the specified interval only after the tick() method returns.
+ */
+ public synchronized void setRegular(boolean regular) {
+ this.regular = regular;
+ }
+
+ /*
+ * This method is used only for testing purposes.
+ */
+ protected Thread getTimerThread() {
+ return TimerThread.timerThread;
+ }
+}
+
+
+/*
+
+This class implements the timer queue and is exclusively used by the
+Timer class. There are only two methods exported to the Timer class -
+enqueue, for inserting a timer into queue and dequeue, for removing
+a timer from the queue.
+
+A timer in the timer queue is awaiting a tick. When a timer is to be
+ticked, it is removed from the timer queue before the owner's tick()
+method is called.
+
+A single timer thread manages the timer queue. This timer thread
+looks at the head of the timer queue and delays until it's time for
+the timer to tick. When the time comes, the timer thread creates a
+callback thread to call the timer owner's tick() method. The timer
+thread then processes the next timer in the queue.
+
+When a timer is inserted at the head of the queue, the timer thread is
+notified. This causes the timer thread to prematurely wake up and
+process the new head of the queue.
+
+*/
+
+class TimerThread extends Thread {
+ /*
+ * Set to true to get debugging output.
+ */
+ public static boolean debug = false;
+
+ /*
+ * This is a handle to the thread managing the thread queue.
+ */
+ static TimerThread timerThread;
+
+ /*
+ * This flag is set if the timer thread has been notified
+ * while it was in the timed wait. This flag allows the
+ * timer thread to tell whether or not the wait completed.
+ */
+ static boolean notified = false;
+
+ protected TimerThread() {
+ super("TimerThread");
+ timerThread = this;
+ start();
+ }
+
+ public synchronized void run() {
+ while (true) {
+ long delay;
+
+ while (timerQueue == null) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ // Just drop through and check timerQueue.
+ }
+ }
+ notified = false;
+ delay = timerQueue.sleepUntil - System.currentTimeMillis();
+ if (delay > 0) {
+ try {
+ wait(delay);
+ } catch (InterruptedException ex) {
+ // Just drop through.
+ }
+ }
+ // remove from timer queue.
+ if (!notified) {
+ Timer timer = timerQueue;
+ timerQueue = timerQueue.next;
+ TimerTickThread thr = TimerTickThread.call(
+ timer, timer.sleepUntil);
+ if (debug) {
+ long delta = (System.currentTimeMillis() - timer.sleepUntil);
+ System.out.println("tick(" + thr.getName() + ","
+ + timer.interval + ","+delta+ ")");
+ if (delta > 250) {
+ System.out.println("*** BIG DELAY ***");
+ }
+ }
+ }
+ }
+ }
+
+ /* *******************************************************
+ Timer Queue
+ ******************************************************* */
+
+ /*
+ * The timer queue is a queue of timers waiting to tick.
+ */
+ static Timer timerQueue = null;
+
+ /*
+ * Uses timer.sleepUntil to determine where in the queue
+ * to insert the timer object.
+ * A new ticker thread is created only if the timer
+ * is inserted at the beginning of the queue.
+ * The timer must not already be in the queue.
+ * Assumes the caller has the TimerThread monitor.
+ */
+ static protected void enqueue(Timer timer) {
+ Timer prev = null;
+ Timer cur = timerQueue;
+
+ if (cur == null || timer.sleepUntil <= cur.sleepUntil) {
+ // insert at front of queue
+ timer.next = timerQueue;
+ timerQueue = timer;
+ notified = true;
+ timerThread.notify();
+ } else {
+ do {
+ prev = cur;
+ cur = cur.next;
+ } while (cur != null && timer.sleepUntil > cur.sleepUntil);
+ // insert or append to the timer queue
+ timer.next = cur;
+ prev.next = timer;
+ }
+ if (debug) {
+ long now = System.currentTimeMillis();
+
+ System.out.print(Thread.currentThread().getName()
+ + ": enqueue " + timer.interval + ": ");
+ cur = timerQueue;
+ while(cur != null) {
+ long delta = cur.sleepUntil - now;
+ System.out.print(cur.interval + "(" + delta + ") ");
+ cur = cur.next;
+ }
+ System.out.println();
+ }
+ }
+
+ /*
+ * If the timer is not in the queue, returns false;
+ * otherwise removes the timer from the timer queue and returns true.
+ * Assumes the caller has the TimerThread monitor.
+ */
+ static protected boolean dequeue(Timer timer) {
+ Timer prev = null;
+ Timer cur = timerQueue;
+
+ while (cur != null && cur != timer) {
+ prev = cur;
+ cur = cur.next;
+ }
+ if (cur == null) {
+ if (debug) {
+ System.out.println(Thread.currentThread().getName()
+ + ": dequeue " + timer.interval + ": no-op");
+ }
+ return false;
+ } if (prev == null) {
+ timerQueue = timer.next;
+ notified = true;
+ timerThread.notify();
+ } else {
+ prev.next = timer.next;
+ }
+ timer.next = null;
+ if (debug) {
+ long now = System.currentTimeMillis();
+
+ System.out.print(Thread.currentThread().getName()
+ + ": dequeue " + timer.interval + ": ");
+ cur = timerQueue;
+ while(cur != null) {
+ long delta = cur.sleepUntil - now;
+ System.out.print(cur.interval + "(" + delta + ") ");
+ cur = cur.next;
+ }
+ System.out.println();
+ }
+ return true;
+ }
+
+ /*
+ * Inserts the timer back into the queue. This method
+ * is used by a callback thread after it has called the
+ * timer owner's tick() method. This method recomputes
+ * the sleepUntil field.
+ * Assumes the caller has the TimerThread and Timer monitor.
+ */
+ protected static void requeue(Timer timer) {
+ if (!timer.stopped) {
+ long now = System.currentTimeMillis();
+ if (timer.regular) {
+ timer.sleepUntil += timer.interval;
+ } else {
+ timer.sleepUntil = now + timer.interval;
+ }
+ enqueue(timer);
+ } else if (debug) {
+ System.out.println(Thread.currentThread().getName()
+ + ": requeue " + timer.interval + ": no-op");
+ }
+ }
+}
+
+/*
+
+This class implements a simple thread whose only purpose is to call a
+timer owner's tick() method. A small fixed-sized pool of threads is
+maintained and is protected by the class monitor. If the pool is
+exhausted, a new thread is temporarily created and destroyed when
+done.
+
+A thread that's in the pool waits on it's own monitor. When the
+thread is retrieved from the pool, the retriever notifies the thread's
+monitor.
+
+*/
+
+class TimerTickThread extends Thread {
+ /*
+ * Maximum size of the thread pool.
+ */
+ static final int MAX_POOL_SIZE = 3;
+
+ /*
+ * Number of threads in the pool.
+ */
+ static int curPoolSize = 0;
+
+ /*
+ * The pool of timer threads.
+ */
+ static TimerTickThread pool = null;
+
+ /*
+ * Is used when linked into the thread pool.
+ */
+ TimerTickThread next = null;
+
+ /*
+ * This is the handle to the timer whose owner's
+ * tick() method will be called.
+ */
+ Timer timer;
+
+ /*
+ * The value of a timer's sleepUntil value is captured here.
+ * This is used to determine whether or not the timer should
+ * be reinserted into the queue. If the timer's sleepUntil
+ * value has changed, the timer is not reinserted.
+ */
+ long lastSleepUntil;
+
+ /*
+ * Creates a new callback thread to call the timer owner's
+ * tick() method. A thread is taken from the pool if one
+ * is available, otherwise, a new thread is created.
+ * The thread handle is returned.
+ */
+ protected static synchronized TimerTickThread call(
+ Timer timer, long sleepUntil) {
+ TimerTickThread thread = pool;
+
+ if (thread == null) {
+ // create one.
+ thread = new TimerTickThread();
+ thread.timer = timer;
+ thread.lastSleepUntil = sleepUntil;
+ thread.start();
+ } else {
+ pool = pool.next;
+ thread.timer = timer;
+ thread.lastSleepUntil = sleepUntil;
+ synchronized (thread) {
+ thread.notify();
+ }
+ }
+ return thread;
+ }
+
+ /*
+ * Returns false if the thread should simply exit;
+ * otherwise the thread is returned the pool, where
+ * it waits to be notified. (I did try to use the
+ * class monitor but the time between the notify
+ * and breaking out of the wait seemed to take
+ * significantly longer; need to look into this later.)
+ */
+ private boolean returnToPool() {
+ synchronized (getClass()) {
+ if (curPoolSize >= MAX_POOL_SIZE) {
+ return false;
+ }
+ next = pool;
+ pool = this;
+ curPoolSize++;
+ timer = null;
+ }
+ while (timer == null) {
+ synchronized (this) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ // Just drop through and retest timer.
+ }
+ }
+ }
+ synchronized (getClass()) {
+ curPoolSize--;
+ }
+ return true;
+ }
+
+ public void run() {
+ do {
+ timer.owner.tick(timer);
+ synchronized (TimerThread.timerThread) {
+ synchronized (timer) {
+ if (lastSleepUntil == timer.sleepUntil) {
+ TimerThread.requeue(timer);
+ }
+ }
+ }
+ } while (returnToPool());
+ }
+}