|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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 { |
|
|
|
|
|
|
|
*/ |
|
public Timeable owner; |
|
|
|
|
|
|
|
*/ |
|
long interval; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
long sleepUntil; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
long remainingTime; |
|
|
|
|
|
|
|
*/ |
|
boolean regular; |
|
|
|
|
|
|
|
*/ |
|
boolean stopped; |
|
|
|
/* ************************************************************** |
|
* Timer queue-related variables |
|
* ************************************************************** */ |
|
|
|
|
|
|
|
|
|
*/ |
|
Timer next; |
|
|
|
/* ************************************************************** |
|
* Timer methods |
|
* ************************************************************** */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static TimerThread timerThread = null; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public synchronized boolean isStopped() { |
|
return stopped; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void stop() { |
|
long now = System.currentTimeMillis(); |
|
|
|
synchronized (timerThread) { |
|
synchronized (this) { |
|
if (!stopped) { |
|
TimerThread.dequeue(this); |
|
remainingTime = Math.max(0, sleepUntil - now); |
|
sleepUntil = now; |
|
stopped = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
sleepUntil = Math.max(sleepUntil + 1, |
|
System.currentTimeMillis() + remainingTime); |
|
TimerThread.enqueue(this); |
|
stopped = false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public void reset() { |
|
synchronized (timerThread) { |
|
synchronized (this) { |
|
setRemainingTime(interval); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized long getStopTime() { |
|
return sleepUntil; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public synchronized long getInterval() { |
|
return interval; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void setInterval(long interval) { |
|
this.interval = interval; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized long getRemainingTime() { |
|
return remainingTime; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void setRemainingTime(long time) { |
|
synchronized (timerThread) { |
|
synchronized (this) { |
|
if (stopped) { |
|
remainingTime = time; |
|
} else { |
|
stop(); |
|
remainingTime = time; |
|
cont(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void setRegular(boolean regular) { |
|
this.regular = regular; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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 { |
|
|
|
|
|
*/ |
|
public static boolean debug = false; |
|
|
|
|
|
|
|
*/ |
|
static TimerThread timerThread; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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. |
|
} |
|
} |
|
|
|
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 |
|
******************************************************* */ |
|
|
|
|
|
|
|
*/ |
|
static Timer timerQueue = null; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static protected void enqueue(Timer timer) { |
|
Timer prev = null; |
|
Timer cur = timerQueue; |
|
|
|
if (cur == null || timer.sleepUntil <= cur.sleepUntil) { |
|
|
|
timer.next = timerQueue; |
|
timerQueue = timer; |
|
notified = true; |
|
timerThread.notify(); |
|
} else { |
|
do { |
|
prev = cur; |
|
cur = cur.next; |
|
} while (cur != null && timer.sleepUntil > cur.sleepUntil); |
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 { |
|
|
|
|
|
*/ |
|
static final int MAX_POOL_SIZE = 3; |
|
|
|
|
|
|
|
*/ |
|
static int curPoolSize = 0; |
|
|
|
|
|
|
|
*/ |
|
static TimerTickThread pool = null; |
|
|
|
|
|
|
|
*/ |
|
TimerTickThread next = null; |
|
|
|
|
|
|
|
|
|
*/ |
|
Timer timer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
long lastSleepUntil; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected static synchronized TimerTickThread call( |
|
Timer timer, long sleepUntil) { |
|
TimerTickThread thread = pool; |
|
|
|
if (thread == null) { |
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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()); |
|
} |
|
} |