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 }