View Javadoc
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      * &gt; 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      * &gt;= 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 }