//******************************************************************************
//
// File: TimerThread.java
// Package: edu.rit.util
// Unit: Class edu.rit.util.TimerThread
//
// This Java source file is copyright (C) 2002-2004 by Alan Kaminsky. All rights
// reserved. For further information, contact the author, Alan Kaminsky, at
// ark@cs.rit.edu.
//
// This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
// software; you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// PJ 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 for more details.
//
// A copy of the GNU General Public License is provided in the file gpl.txt. You
// may also obtain a copy of the GNU General Public License on the World Wide
// Web at http://www.gnu.org/licenses/gpl.html.
//
//******************************************************************************
package edu.rit.util;
import java.util.Iterator;
import java.util.Vector;
/**
* Class TimerThread encapsulates a thread that does the timing for {@linkplain
* Timer}s and performs {@linkplain TimerTask}s' actions when timeouts occur.
*
* A timer is created by calling a timer thread's createTimer() method,
* giving a timer task to associate with the timer. Multiple timers may be
* created under the control of the same timer thread. The timer thread will
* perform the actions of its timers' timer tasks one at a time at the proper
* instants (by causing each timer to call its timer task's action()
* method). Note that the timer tasks of a single timer thread are performed
* sequentially, which may cause some timer tasks' actions to be delayed
* depending on how long other timer tasks' actions take to execute. If
* necessary, consider running separate timers in separate timer threads so the
* timer tasks' actions run concurrently.
*
* Class TimerThread does not offer real-time guarantees. It merely makes
* a best-effort attempt to perform each timer task's actions as soon as
* possible after the timeouts occur.
*
* A TimerThread is just a {@linkplain java.lang.Thread Thread}. After
* constructing a timer thread, you can mark it as a daemon thread if you want.
* You must also call the timer thread's start() method, or no timeouts
* will occur. To gracefully stop a timer thread, call the shutdown()
* method.
*
* To simplify writing programs with multiple objects that all use the same
* timer thread, the static TimerThread.getDefault() method returns a
* single shared instance of class TimerThread. The default timer thread is
* marked as a daemon thread and is started automatically. The default timer
* thread is not created until the first call to
* TimerThread.getDefault().
*
* Classes {@linkplain Timer}, {@linkplain TimerTask}, and TimerThread provide
* capabilities similar to classes java.util.Timer and java.util.TimerTask.
* Unlike the latter, they also provide the ability to stop and restart a timer
* and the ability to deal with race conditions in multithreaded programs.
*
* @author Alan Kaminsky
* @version 18-Jun-2003
*/
public class TimerThread
extends Thread
{
// Hidden helper classes.
/**
* Class TimerThread.TimeoutInfo is a record used to keep track of a
* timeout.
*
* @author Alan Kaminsky
* @version 18-Sep-2002
*/
private static class TimeoutInfo
{
// Instant at which the timeout will occur (milliseconds since midnight
// 01-Jan-1970 UTC).
public long myTimeout;
// Timer to trigger.
public Timer myTimer;
public TimeoutInfo
(long theTimeout,
Timer theTimer)
{
myTimeout = theTimeout;
myTimer = theTimer;
}
}
// Hidden data members.
/**
* Queue of timeout info records, organized as a heap in order of timeout.
* Index 0 is unused. Indexes 1 .. mySize contain the heap. The queue's
* length is increased by INCR when necessary to add a new record.
*/
private static final int INCR = 4;
private TimeoutInfo[] myQueue = new TimeoutInfo [INCR+1];
/**
* Number of records in the queue.
*/
private int mySize = 0;
/**
* True if this timer thread is running, false if it's shut down.
*/
private boolean iamRunning = true;
// Hidden static data members.
/**
* The default timer thread.
*/
private static TimerThread theDefaultTimerThread = null;
// Exported constructors.
/**
* Construct a new timer thread. After constructing it, you must call the
* timer thread's start() method, or no timeouts will occur.
*/
public TimerThread()
{
super();
}
// Exported operations.
/**
* Get the default timer thread, a single shared instance of class
* TimerThread. The default timer thread is marked as a daemon thread and is
* started automatically. The default timer thread is not created until the
* first call to TimerThread.getDefault().
*
* @return Default timer thread.
*/
public static synchronized TimerThread getDefault()
{
if (theDefaultTimerThread == null)
{
theDefaultTimerThread = new TimerThread();
theDefaultTimerThread.setDaemon (true);
theDefaultTimerThread.start();
}
return theDefaultTimerThread;
}
/**
* Create a new timer associated with the given timer task and under the
* control of this timer thread. When the timer is triggered, this timer
* thread will cause the timer to call the given timer task's
* action() method.
*
* @param theTimerTask Timer task.
*
* @exception NullPointerException
* (unchecked exception) Thrown if theTimerTask is null.
*/
public Timer createTimer
(TimerTask theTimerTask)
{
return new Timer (this, theTimerTask);
}
/**
* Shut down this timer thread.
*/
public void shutdown()
{
synchronized (this)
{
iamRunning = false;
notifyAll();
}
}
/**
* Perform this timer thread's processing. (Never call the run()
* method yourself!)
*
* @exception IllegalStateException
* (unchecked exception) Thrown if some thread other than this timer
* thread called the run() method.
*/
public void run()
{
// Only this timer thread itself can call the run() method.
if (Thread.currentThread() != this)
{
throw new IllegalStateException
("Wrong thread called the run() method");
}
try
{
while (iamRunning)
{
long now = System.currentTimeMillis();
Vector theTriggeredTimeouts = null;
synchronized (this)
{
// If timeout queue is empty, wait until notified.
if (mySize == 0)
{
wait();
now = System.currentTimeMillis();
}
// If timeout queue is not empty and first timeout is in the
// future, wait until timeout or until notified.
else
{
long waitTime = myQueue[1].myTimeout - now;
if (waitTime > 0L)
{
wait (waitTime);
now = System.currentTimeMillis();
}
}
// Pull all timeouts that have occurred out of the timeout
// queue into a separate list.
theTriggeredTimeouts = new Vector();
while (mySize > 0 && myQueue[1].myTimeout <= now)
{
theTriggeredTimeouts.add (myQueue[1]);
myQueue[1] = myQueue[mySize];
myQueue[mySize] = null;
-- mySize;
siftDown (mySize);
}
}
// Perform the action of each triggered timeout. Do this outside
// the synchronized block, or a deadlock may happen if a timer
// is restarted.
Iterator iter = theTriggeredTimeouts.iterator();
while (iter.hasNext())
{
((TimeoutInfo) iter.next()).myTimer.trigger (now);
}
}
}
catch (InterruptedException exc)
{
System.err.println ("TimerThread interrupted");
exc.printStackTrace (System.err);
}
}
// Hidden operations.
/**
* Schedule a timer.
*
* @param theTimeout Timeout instant.
* @param theTimer Timer.
*/
synchronized void schedule
(long theTimeout,
Timer theTimer)
{
// Increase queue allocation if necessary.
if (mySize == myQueue.length - 1)
{
TimeoutInfo[] newQueue = new TimeoutInfo [myQueue.length + INCR];
System.arraycopy (myQueue, 1, newQueue, 1, mySize);
myQueue = newQueue;
}
// Add a new timeout info record to the queue.
++ mySize;
myQueue[mySize] = new TimeoutInfo (theTimeout, theTimer);
siftUp (mySize);
// Wake up the timer thread.
notifyAll();
}
/**
* Sift up the last element in the heap. Precondition: HEAP(1,n-1) and n
* > 0. Postcondition: HEAP(1,n).
*/
private void siftUp
(int n)
{
int i = n;
int p;
TimeoutInfo temp;
for (;;)
{
// Invariant: HEAP(1,n) except perhaps between i and its parent.
if (i == 1) break;
p = i / 2;
if (myQueue[p].myTimeout <= myQueue[i].myTimeout) break;
temp = myQueue[p];
myQueue[p] = myQueue[i];
myQueue[i] = temp;
i = p;
}
}
/**
* Sift down the first element in the heap. Precondition: HEAP(2,n) and n
* >= 0. Postcondition: HEAP(1,n).
*/
private void siftDown
(int n)
{
int i = 1;
int c;
TimeoutInfo temp;
for (;;)
{
// Invariant: HEAP(1,n) except perhaps between i and its 0, 1, or 2
// children.
c = 2 * i;
if (c > n) break;
// c is the left child of i.
if (c+1 <= n)
{
// c+1 is the right child of i.
if (myQueue[c+1].myTimeout < myQueue[c].myTimeout)
{
c = c + 1;
}
}
// c is the least child of i.
if (myQueue[i].myTimeout <= myQueue[c].myTimeout) break;
temp = myQueue[c];
myQueue[c] = myQueue[i];
myQueue[i] = temp;
i = c;
}
}
// Unit test main program.
// /**
// * Helper class for unit test main program.
// */
// private static class Message
// implements TimerTask
// {
// private String myMessage;
//
// public Message
// (String theMessage)
// {
// myMessage = theMessage;
// }
//
// public void action
// (Timer theTimer)
// {
// System.out.println (myMessage);
// }
// }
//
// /**
// * Unit test main program.
// */
// public static void main
// (String[] args)
// {
// try
// {
// TimerThread theTimerThread = new TimerThread();
// theTimerThread.start();
//
// Timer timer1 =
// theTimerThread.createTimer
// (new Message ("Timer 1 triggered"));
//
// Timer timer2 =
// theTimerThread.createTimer
// (new Message ("Timer 2 triggered"));
//
// Timer timer3 =
// theTimerThread.createTimer
// (new Message ("Timer 3 triggered"));
//
// timer1.start (1000L, 1000L);
// timer2.start (5000L, 5000L);
// timer3.start (10000L);
// }
// catch (Throwable exc)
// {
// System.err.println ("edu.rit.util.TimerThread: Uncaught exception");
// exc.printStackTrace (System.err);
// }
// }
}
Ads help maintain this website.