1 //******************************************************************************
2 //
3 // File: TimerThread.java
4 // Package: edu.rit.util
5 // Unit: Class edu.rit.util.TimerThread
6 //
7 // This Java source file is copyright (C) 2002-2004 by Alan Kaminsky. All rights
8 // reserved. For further information, contact the author, Alan Kaminsky, at
9 // ark@cs.rit.edu.
10 //
11 // This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
12 // software; you can redistribute it and/or modify it under the terms of the GNU
13 // General Public License as published by the Free Software Foundation; either
14 // version 3 of the License, or (at your option) any later version.
15 //
16 // PJ is distributed in the hope that it will be useful, but WITHOUT ANY
17 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18 // A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19 //
20 // Linking this library statically or dynamically with other modules is making a
21 // combined work based on this library. Thus, the terms and conditions of the GNU
22 // General Public License cover the whole combination.
23 //
24 // As a special exception, the copyright holders of this library give you
25 // permission to link this library with independent modules to produce an
26 // executable, regardless of the license terms of these independent modules, and
27 // to copy and distribute the resulting executable under terms of your choice,
28 // provided that you also meet, for each linked independent module, the terms
29 // and conditions of the license of that module. An independent module is a module
30 // which is not derived from or based on this library. If you modify this library,
31 // you may extend this exception to your version of the library, but you are not
32 // obligated to do so. If you do not wish to do so, delete this exception
33 // statement from your version.
34 //
35 // A copy of the GNU General Public License is provided in the file gpl.txt. You
36 // may also obtain a copy of the GNU General Public License on the World Wide
37 // Web at http://www.gnu.org/licenses/gpl.html.
38 //
39 //******************************************************************************
40 package edu.rit.util;
41
42 import java.util.Iterator;
43 import java.util.Vector;
44
45 /**
46 * Class TimerThread encapsulates a thread that does the timing for {@linkplain
47 * Timer}s and performs {@linkplain TimerTask}s' actions when timeouts occur.
48 * <P>
49 * A timer is created by calling a timer thread's <code>createTimer()</code> method,
50 * giving a timer task to associate with the timer. Multiple timers may be
51 * created under the control of the same timer thread. The timer thread will
52 * perform the actions of its timers' timer tasks one at a time at the proper
53 * instants (by causing each timer to call its timer task's <code>action()</code>
54 * method). Note that the timer tasks of a single timer thread are performed
55 * sequentially, which may cause some timer tasks' actions to be delayed
56 * depending on how long other timer tasks' actions take to execute. If
57 * necessary, consider running separate timers in separate timer threads so the
58 * timer tasks' actions run concurrently.
59 * <P>
60 * Class TimerThread does <I>not</I> offer real-time guarantees. It merely makes
61 * a best-effort attempt to perform each timer task's actions as soon as
62 * possible after the timeouts occur.
63 * <P>
64 * A TimerThread is just a {@linkplain java.lang.Thread Thread}. After
65 * constructing a timer thread, you can mark it as a daemon thread if you want.
66 * You must also call the timer thread's <code>start()</code> method, or no timeouts
67 * will occur. To gracefully stop a timer thread, call the <code>shutdown()</code>
68 * method.
69 * <P>
70 * To simplify writing programs with multiple objects that all use the same
71 * timer thread, the static <code>TimerThread.getDefault()</code> method returns a
72 * single shared instance of class TimerThread. The default timer thread is
73 * marked as a daemon thread and is started automatically. The default timer
74 * thread is not created until the first call to
75 * <code>TimerThread.getDefault()</code>.
76 * <P>
77 * Classes {@linkplain Timer}, {@linkplain TimerTask}, and TimerThread provide
78 * capabilities similar to classes java.util.Timer and java.util.TimerTask.
79 * Unlike the latter, they also provide the ability to stop and restart a timer
80 * and the ability to deal with race conditions in multithreaded programs.
81 *
82 * @author Alan Kaminsky
83 * @version 18-Jun-2003
84 */
85 public class TimerThread
86 extends Thread {
87
88 // Hidden helper classes.
89 /**
90 * Class TimerThread.TimeoutInfo is a record used to keep track of a
91 * timeout.
92 *
93 * @author Alan Kaminsky
94 * @version 18-Sep-2002
95 */
96 private static class TimeoutInfo {
97 // Instant at which the timeout will occur (milliseconds since midnight
98 // 01-Jan-1970 UTC).
99
100 public long myTimeout;
101
102 // Timer to trigger.
103 public Timer myTimer;
104
105 public TimeoutInfo(long theTimeout,
106 Timer theTimer) {
107 myTimeout = theTimeout;
108 myTimer = theTimer;
109 }
110 }
111
112 // Hidden data members.
113 /**
114 * Queue of timeout info records, organized as a heap in order of timeout.
115 * Index 0 is unused. Indexes 1 .. mySize contain the heap. The queue's
116 * length is increased by INCR when necessary to add a new record.
117 */
118 private static final int INCR = 4;
119 private TimeoutInfo[] myQueue = new TimeoutInfo[INCR + 1];
120
121 /**
122 * Number of records in the queue.
123 */
124 private int mySize = 0;
125
126 /**
127 * True if this timer thread is running, false if it's shut down.
128 */
129 private boolean iamRunning = true;
130
131 // Hidden static data members.
132 /**
133 * The default timer thread.
134 */
135 private static TimerThread theDefaultTimerThread = null;
136
137 // Exported constructors.
138 /**
139 * Construct a new timer thread. After constructing it, you must call the
140 * timer thread's <code>start()</code> method, or no timeouts will occur.
141 */
142 public TimerThread() {
143 super();
144 }
145
146 // Exported operations.
147 /**
148 * Get the default timer thread, a single shared instance of class
149 * TimerThread. The default timer thread is marked as a daemon thread and is
150 * started automatically. The default timer thread is not created until the
151 * first call to <code>TimerThread.getDefault()</code>.
152 *
153 * @return Default timer thread.
154 */
155 public static synchronized TimerThread getDefault() {
156 if (theDefaultTimerThread == null) {
157 theDefaultTimerThread = new TimerThread();
158 theDefaultTimerThread.setDaemon(true);
159 theDefaultTimerThread.start();
160 }
161 return theDefaultTimerThread;
162 }
163
164 /**
165 * Create a new timer associated with the given timer task and under the
166 * control of this timer thread. When the timer is triggered, this timer
167 * thread will cause the timer to call the given timer task's
168 * <code>action()</code> method.
169 *
170 * @param theTimerTask Timer task.
171 * @exception NullPointerException (unchecked exception) Thrown if
172 * <code>theTimerTask</code> is null.
173 * @return a {@link edu.rit.util.Timer} object.
174 */
175 public Timer createTimer(TimerTask theTimerTask) {
176 return new Timer(this, theTimerTask);
177 }
178
179 /**
180 * Shut down this timer thread.
181 */
182 public void shutdown() {
183 synchronized (this) {
184 iamRunning = false;
185 notifyAll();
186 }
187 }
188
189 /**
190 * Perform this timer thread's processing. (Never call the <code>run()</code>
191 * method yourself!)
192 *
193 * @exception IllegalStateException (unchecked exception) Thrown if some
194 * thread other than this timer thread called the <code>run()</code> method.
195 */
196 @SuppressWarnings("unchecked")
197 public void run() {
198 // Only this timer thread itself can call the run() method.
199 if (Thread.currentThread() != this) {
200 throw new IllegalStateException("Wrong thread called the run() method");
201 }
202
203 try {
204 while (iamRunning) {
205 long now = System.currentTimeMillis();
206 Vector<TimeoutInfo> theTriggeredTimeouts;
207
208 synchronized (this) {
209 // If timeout queue is empty, wait until notified.
210 if (mySize == 0) {
211 wait();
212 now = System.currentTimeMillis();
213 } // If timeout queue is not empty and first timeout is in the
214 // future, wait until timeout or until notified.
215 else {
216 long waitTime = myQueue[1].myTimeout - now;
217 if (waitTime > 0L) {
218 wait(waitTime);
219 now = System.currentTimeMillis();
220 }
221 }
222
223 // Pull all timeouts that have occurred out of the timeout
224 // queue into a separate list.
225 theTriggeredTimeouts = new Vector<>();
226 while (mySize > 0 && myQueue[1].myTimeout <= now) {
227 theTriggeredTimeouts.add(myQueue[1]);
228 myQueue[1] = myQueue[mySize];
229 myQueue[mySize] = null;
230 --mySize;
231 siftDown(mySize);
232 }
233 }
234
235 // Perform the action of each triggered timeout. Do this outside
236 // the synchronized block, or a deadlock may happen if a timer
237 // is restarted.
238 Iterator<TimeoutInfo> iter = theTriggeredTimeouts.iterator();
239 while (iter.hasNext()) {
240 iter.next().myTimer.trigger(now);
241 }
242 }
243 } catch (InterruptedException exc) {
244 System.err.println("TimerThread interrupted");
245 exc.printStackTrace(System.err);
246 }
247 }
248
249 // Hidden operations.
250 /**
251 * Schedule a timer.
252 *
253 * @param theTimeout Timeout instant.
254 * @param theTimer Timer.
255 */
256 synchronized void schedule(long theTimeout,
257 Timer theTimer) {
258 // Increase queue allocation if necessary.
259 if (mySize == myQueue.length - 1) {
260 TimeoutInfo[] newQueue = new TimeoutInfo[myQueue.length + INCR];
261 System.arraycopy(myQueue, 1, newQueue, 1, mySize);
262 myQueue = newQueue;
263 }
264
265 // Add a new timeout info record to the queue.
266 ++mySize;
267 myQueue[mySize] = new TimeoutInfo(theTimeout, theTimer);
268 siftUp(mySize);
269
270 // Wake up the timer thread.
271 notifyAll();
272 }
273
274 /**
275 * Sift up the last element in the heap. Precondition: HEAP(1,n-1) and n
276 * > 0. Postcondition: HEAP(1,n).
277 */
278 private void siftUp(int n) {
279 int i = n;
280 int p;
281 TimeoutInfo temp;
282 for (;;) {
283 // Invariant: HEAP(1,n) except perhaps between i and its parent.
284 if (i == 1) {
285 break;
286 }
287 p = i / 2;
288 if (myQueue[p].myTimeout <= myQueue[i].myTimeout) {
289 break;
290 }
291 temp = myQueue[p];
292 myQueue[p] = myQueue[i];
293 myQueue[i] = temp;
294 i = p;
295 }
296 }
297
298 /**
299 * Sift down the first element in the heap. Precondition: HEAP(2,n) and n
300 * >= 0. Postcondition: HEAP(1,n).
301 */
302 private void siftDown(int n) {
303 int i = 1;
304 int c;
305 TimeoutInfo temp;
306 for (;;) {
307 // Invariant: HEAP(1,n) except perhaps between i and its 0, 1, or 2
308 // children.
309 c = 2 * i;
310 if (c > n) {
311 break;
312 }
313 // c is the left child of i.
314 if (c + 1 <= n) {
315 // c+1 is the right child of i.
316 if (myQueue[c + 1].myTimeout < myQueue[c].myTimeout) {
317 c = c + 1;
318 }
319 }
320 // c is the least child of i.
321 if (myQueue[i].myTimeout <= myQueue[c].myTimeout) {
322 break;
323 }
324 temp = myQueue[c];
325 myQueue[c] = myQueue[i];
326 myQueue[i] = temp;
327 i = c;
328 }
329 }
330
331 // Unit test main program.
332 // /**
333 // * Helper class for unit test main program.
334 // */
335 // private static class Message
336 // implements TimerTask
337 // {
338 // private String myMessage;
339 //
340 // public Message
341 // (String theMessage)
342 // {
343 // myMessage = theMessage;
344 // }
345 //
346 // public void action
347 // (Timer theTimer)
348 // {
349 // System.out.println (myMessage);
350 // }
351 // }
352 //
353 // /**
354 // * Unit test main program.
355 // */
356 // public static void main
357 // (String[] args)
358 // {
359 // try
360 // {
361 // TimerThread theTimerThread = new TimerThread();
362 // theTimerThread.start();
363 //
364 // Timer timer1 =
365 // theTimerThread.createTimer
366 // (new Message ("Timer 1 triggered"));
367 //
368 // Timer timer2 =
369 // theTimerThread.createTimer
370 // (new Message ("Timer 2 triggered"));
371 //
372 // Timer timer3 =
373 // theTimerThread.createTimer
374 // (new Message ("Timer 3 triggered"));
375 //
376 // timer1.start (1000L, 1000L);
377 // timer2.start (5000L, 5000L);
378 // timer3.start (10000L);
379 // }
380 // catch (Throwable exc)
381 // {
382 // System.err.println ("edu.rit.util.TimerThread: Uncaught exception");
383 // exc.printStackTrace (System.err);
384 // }
385 // }
386 }