GNU Classpath (0.17) | ||
Frames | No Frames |
1: /* Timer.java -- Timer that runs TimerTasks at a later time. 2: Copyright (C) 2000, 2001 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package java.util; 39: 40: /** 41: * Timer that can run TimerTasks at a later time. 42: * TimerTasks can be scheduled for one time execution at some time in the 43: * future. They can be scheduled to be rescheduled at a time period after the 44: * task was last executed. Or they can be scheduled to be executed repeatedly 45: * at a fixed rate. 46: * <p> 47: * The normal scheduling will result in a more or less even delay in time 48: * between successive executions, but the executions could drift in time if 49: * the task (or other tasks) takes a long time to execute. Fixed delay 50: * scheduling guarantees more or less that the task will be executed at a 51: * specific time, but if there is ever a delay in execution then the period 52: * between successive executions will be shorter. The first method of 53: * repeated scheduling is preferred for repeated tasks in response to user 54: * interaction, the second method of repeated scheduling is preferred for tasks 55: * that act like alarms. 56: * <p> 57: * The Timer keeps a binary heap as a task priority queue which means that 58: * scheduling and serving of a task in a queue of n tasks costs O(log n). 59: * 60: * @see TimerTask 61: * @since 1.3 62: * @author Mark Wielaard (mark@klomp.org) 63: */ 64: public class Timer 65: { 66: /** 67: * Priority Task Queue. 68: * TimerTasks are kept in a binary heap. 69: * The scheduler calls sleep() on the queue when it has nothing to do or 70: * has to wait. A sleeping scheduler can be notified by calling interrupt() 71: * which is automatically called by the enqueue(), cancel() and 72: * timerFinalized() methods. 73: */ 74: private static final class TaskQueue 75: { 76: /** Default size of this queue */ 77: private static final int DEFAULT_SIZE = 32; 78: 79: /** Whether to return null when there is nothing in the queue */ 80: private boolean nullOnEmpty; 81: 82: /** 83: * The heap containing all the scheduled TimerTasks 84: * sorted by the TimerTask.scheduled field. 85: * Null when the stop() method has been called. 86: */ 87: private TimerTask heap[]; 88: 89: /** 90: * The actual number of elements in the heap 91: * Can be less then heap.length. 92: * Note that heap[0] is used as a sentinel. 93: */ 94: private int elements; 95: 96: /** 97: * Creates a TaskQueue of default size without any elements in it. 98: */ 99: public TaskQueue() 100: { 101: heap = new TimerTask[DEFAULT_SIZE]; 102: elements = 0; 103: nullOnEmpty = false; 104: } 105: 106: /** 107: * Adds a TimerTask at the end of the heap. 108: * Grows the heap if necessary by doubling the heap in size. 109: */ 110: private void add(TimerTask task) 111: { 112: elements++; 113: if (elements == heap.length) 114: { 115: TimerTask new_heap[] = new TimerTask[heap.length * 2]; 116: System.arraycopy(heap, 0, new_heap, 0, heap.length); 117: heap = new_heap; 118: } 119: heap[elements] = task; 120: } 121: 122: /** 123: * Removes the last element from the heap. 124: * Shrinks the heap in half if 125: * elements+DEFAULT_SIZE/2 <= heap.length/4. 126: */ 127: private void remove() 128: { 129: // clear the entry first 130: heap[elements] = null; 131: elements--; 132: if (elements + DEFAULT_SIZE / 2 <= (heap.length / 4)) 133: { 134: TimerTask new_heap[] = new TimerTask[heap.length / 2]; 135: System.arraycopy(heap, 0, new_heap, 0, elements + 1); 136: heap = new_heap; 137: } 138: } 139: 140: /** 141: * Adds a task to the queue and puts it at the correct place 142: * in the heap. 143: */ 144: public synchronized void enqueue(TimerTask task) 145: { 146: // Check if it is legal to add another element 147: if (heap == null) 148: { 149: throw new IllegalStateException 150: ("cannot enqueue when stop() has been called on queue"); 151: } 152: 153: heap[0] = task; // sentinel 154: add(task); // put the new task at the end 155: // Now push the task up in the heap until it has reached its place 156: int child = elements; 157: int parent = child / 2; 158: while (heap[parent].scheduled > task.scheduled) 159: { 160: heap[child] = heap[parent]; 161: child = parent; 162: parent = child / 2; 163: } 164: // This is the correct place for the new task 165: heap[child] = task; 166: heap[0] = null; // clear sentinel 167: // Maybe sched() is waiting for a new element 168: this.notify(); 169: } 170: 171: /** 172: * Returns the top element of the queue. 173: * Can return null when no task is in the queue. 174: */ 175: private TimerTask top() 176: { 177: if (elements == 0) 178: { 179: return null; 180: } 181: else 182: { 183: return heap[1]; 184: } 185: } 186: 187: /** 188: * Returns the top task in the Queue. 189: * Removes the element from the heap and reorders the heap first. 190: * Can return null when there is nothing in the queue. 191: */ 192: public synchronized TimerTask serve() 193: { 194: // The task to return 195: TimerTask task = null; 196: 197: while (task == null) 198: { 199: // Get the next task 200: task = top(); 201: 202: // return null when asked to stop 203: // or if asked to return null when the queue is empty 204: if ((heap == null) || (task == null && nullOnEmpty)) 205: { 206: return null; 207: } 208: 209: // Do we have a task? 210: if (task != null) 211: { 212: // The time to wait until the task should be served 213: long time = task.scheduled - System.currentTimeMillis(); 214: if (time > 0) 215: { 216: // This task should not yet be served 217: // So wait until this task is ready 218: // or something else happens to the queue 219: task = null; // set to null to make sure we call top() 220: try 221: { 222: this.wait(time); 223: } 224: catch (InterruptedException _) 225: { 226: } 227: } 228: } 229: else 230: { 231: // wait until a task is added 232: // or something else happens to the queue 233: try 234: { 235: this.wait(); 236: } 237: catch (InterruptedException _) 238: { 239: } 240: } 241: } 242: 243: // reconstruct the heap 244: TimerTask lastTask = heap[elements]; 245: remove(); 246: 247: // drop lastTask at the beginning and move it down the heap 248: int parent = 1; 249: int child = 2; 250: heap[1] = lastTask; 251: while (child <= elements) 252: { 253: if (child < elements) 254: { 255: if (heap[child].scheduled > heap[child + 1].scheduled) 256: { 257: child++; 258: } 259: } 260: 261: if (lastTask.scheduled <= heap[child].scheduled) 262: break; // found the correct place (the parent) - done 263: 264: heap[parent] = heap[child]; 265: parent = child; 266: child = parent * 2; 267: } 268: 269: // this is the correct new place for the lastTask 270: heap[parent] = lastTask; 271: 272: // return the task 273: return task; 274: } 275: 276: /** 277: * When nullOnEmpty is true the serve() method will return null when 278: * there are no tasks in the queue, otherwise it will wait until 279: * a new element is added to the queue. It is used to indicate to 280: * the scheduler that no new tasks will ever be added to the queue. 281: */ 282: public synchronized void setNullOnEmpty(boolean nullOnEmpty) 283: { 284: this.nullOnEmpty = nullOnEmpty; 285: this.notify(); 286: } 287: 288: /** 289: * When this method is called the current and all future calls to 290: * serve() will return null. It is used to indicate to the Scheduler 291: * that it should stop executing since no more tasks will come. 292: */ 293: public synchronized void stop() 294: { 295: this.heap = null; 296: this.elements = 0; 297: this.notify(); 298: } 299: 300: } // TaskQueue 301: 302: /** 303: * The scheduler that executes all the tasks on a particular TaskQueue, 304: * reschedules any repeating tasks and that waits when no task has to be 305: * executed immediatly. Stops running when canceled or when the parent 306: * Timer has been finalized and no more tasks have to be executed. 307: */ 308: private static final class Scheduler implements Runnable 309: { 310: // The priority queue containing all the TimerTasks. 311: private TaskQueue queue; 312: 313: /** 314: * Creates a new Scheduler that will schedule the tasks on the 315: * given TaskQueue. 316: */ 317: public Scheduler(TaskQueue queue) 318: { 319: this.queue = queue; 320: } 321: 322: public void run() 323: { 324: TimerTask task; 325: while ((task = queue.serve()) != null) 326: { 327: // If this task has not been canceled 328: if (task.scheduled >= 0) 329: { 330: 331: // Mark execution time 332: task.lastExecutionTime = task.scheduled; 333: 334: // Repeatable task? 335: if (task.period < 0) 336: { 337: // Last time this task is executed 338: task.scheduled = -1; 339: } 340: 341: // Run the task 342: try 343: { 344: task.run(); 345: } 346: catch (ThreadDeath death) 347: { 348: // If an exception escapes, the Timer becomes invalid. 349: queue.stop(); 350: throw death; 351: } 352: catch (Throwable t) 353: { 354: // If an exception escapes, the Timer becomes invalid. 355: queue.stop(); 356: } 357: } 358: 359: // Calculate next time and possibly re-enqueue. 360: if (task.scheduled >= 0) 361: { 362: if (task.fixed) 363: { 364: task.scheduled += task.period; 365: } 366: else 367: { 368: task.scheduled = task.period + System.currentTimeMillis(); 369: } 370: 371: try 372: { 373: queue.enqueue(task); 374: } 375: catch (IllegalStateException ise) 376: { 377: // Ignore. Apparently the Timer queue has been stopped. 378: } 379: } 380: } 381: } 382: } // Scheduler 383: 384: // Number of Timers created. 385: // Used for creating nice Thread names. 386: private static int nr; 387: 388: // The queue that all the tasks are put in. 389: // Given to the scheduler 390: private TaskQueue queue; 391: 392: // The Scheduler that does all the real work 393: private Scheduler scheduler; 394: 395: // Used to run the scheduler. 396: // Also used to checked if the Thread is still running by calling 397: // thread.isAlive(). Sometimes a Thread is suddenly killed by the system 398: // (if it belonged to an Applet). 399: private Thread thread; 400: 401: // When cancelled we don't accept any more TimerTasks. 402: private boolean canceled; 403: 404: /** 405: * Creates a new Timer with a non daemon Thread as Scheduler, with normal 406: * priority and a default name. 407: */ 408: public Timer() 409: { 410: this(false); 411: } 412: 413: /** 414: * Creates a new Timer with a daemon Thread as scheduler if daemon is true, 415: * with normal priority and a default name. 416: */ 417: public Timer(boolean daemon) 418: { 419: this(daemon, Thread.NORM_PRIORITY); 420: } 421: 422: /** 423: * Creates a new Timer with a daemon Thread as scheduler if daemon is true, 424: * with the priority given and a default name. 425: */ 426: private Timer(boolean daemon, int priority) 427: { 428: this(daemon, priority, "Timer-" + (++nr)); 429: } 430: 431: /** 432: * Creates a new Timer with a daemon Thread as scheduler if daemon is true, 433: * with the priority and name given.E 434: */ 435: private Timer(boolean daemon, int priority, String name) 436: { 437: canceled = false; 438: queue = new TaskQueue(); 439: scheduler = new Scheduler(queue); 440: thread = new Thread(scheduler, name); 441: thread.setDaemon(daemon); 442: thread.setPriority(priority); 443: thread.start(); 444: } 445: 446: /** 447: * Cancels the execution of the scheduler. If a task is executing it will 448: * normally finish execution, but no other tasks will be executed and no 449: * more tasks can be scheduled. 450: */ 451: public void cancel() 452: { 453: canceled = true; 454: queue.stop(); 455: } 456: 457: /** 458: * Schedules the task at Time time, repeating every period 459: * milliseconds if period is positive and at a fixed rate if fixed is true. 460: * 461: * @exception IllegalArgumentException if time is negative 462: * @exception IllegalStateException if the task was already scheduled or 463: * canceled or this Timer is canceled or the scheduler thread has died 464: */ 465: private void schedule(TimerTask task, long time, long period, boolean fixed) 466: { 467: if (time < 0) 468: throw new IllegalArgumentException("negative time"); 469: 470: if (task.scheduled == 0 && task.lastExecutionTime == -1) 471: { 472: task.scheduled = time; 473: task.period = period; 474: task.fixed = fixed; 475: } 476: else 477: { 478: throw new IllegalStateException 479: ("task was already scheduled or canceled"); 480: } 481: 482: if (!this.canceled && this.thread != null) 483: { 484: queue.enqueue(task); 485: } 486: else 487: { 488: throw new IllegalStateException 489: ("timer was canceled or scheduler thread has died"); 490: } 491: } 492: 493: private static void positiveDelay(long delay) 494: { 495: if (delay < 0) 496: { 497: throw new IllegalArgumentException("delay is negative"); 498: } 499: } 500: 501: private static void positivePeriod(long period) 502: { 503: if (period < 0) 504: { 505: throw new IllegalArgumentException("period is negative"); 506: } 507: } 508: 509: /** 510: * Schedules the task at the specified data for one time execution. 511: * 512: * @exception IllegalArgumentException if date.getTime() is negative 513: * @exception IllegalStateException if the task was already scheduled or 514: * canceled or this Timer is canceled or the scheduler thread has died 515: */ 516: public void schedule(TimerTask task, Date date) 517: { 518: long time = date.getTime(); 519: schedule(task, time, -1, false); 520: } 521: 522: /** 523: * Schedules the task at the specified date and reschedules the task every 524: * period milliseconds after the last execution of the task finishes until 525: * this timer or the task is canceled. 526: * 527: * @exception IllegalArgumentException if period or date.getTime() is 528: * negative 529: * @exception IllegalStateException if the task was already scheduled or 530: * canceled or this Timer is canceled or the scheduler thread has died 531: */ 532: public void schedule(TimerTask task, Date date, long period) 533: { 534: positivePeriod(period); 535: long time = date.getTime(); 536: schedule(task, time, period, false); 537: } 538: 539: /** 540: * Schedules the task after the specified delay milliseconds for one time 541: * execution. 542: * 543: * @exception IllegalArgumentException if delay or 544: * System.currentTimeMillis + delay is negative 545: * @exception IllegalStateException if the task was already scheduled or 546: * canceled or this Timer is canceled or the scheduler thread has died 547: */ 548: public void schedule(TimerTask task, long delay) 549: { 550: positiveDelay(delay); 551: long time = System.currentTimeMillis() + delay; 552: schedule(task, time, -1, false); 553: } 554: 555: /** 556: * Schedules the task after the delay milliseconds and reschedules the 557: * task every period milliseconds after the last execution of the task 558: * finishes until this timer or the task is canceled. 559: * 560: * @exception IllegalArgumentException if delay or period is negative 561: * @exception IllegalStateException if the task was already scheduled or 562: * canceled or this Timer is canceled or the scheduler thread has died 563: */ 564: public void schedule(TimerTask task, long delay, long period) 565: { 566: positiveDelay(delay); 567: positivePeriod(period); 568: long time = System.currentTimeMillis() + delay; 569: schedule(task, time, period, false); 570: } 571: 572: /** 573: * Schedules the task at the specified date and reschedules the task at a 574: * fixed rate every period milliseconds until this timer or the task is 575: * canceled. 576: * 577: * @exception IllegalArgumentException if period or date.getTime() is 578: * negative 579: * @exception IllegalStateException if the task was already scheduled or 580: * canceled or this Timer is canceled or the scheduler thread has died 581: */ 582: public void scheduleAtFixedRate(TimerTask task, Date date, long period) 583: { 584: positivePeriod(period); 585: long time = date.getTime(); 586: schedule(task, time, period, true); 587: } 588: 589: /** 590: * Schedules the task after the delay milliseconds and reschedules the task 591: * at a fixed rate every period milliseconds until this timer or the task 592: * is canceled. 593: * 594: * @exception IllegalArgumentException if delay or 595: * System.currentTimeMillis + delay is negative 596: * @exception IllegalStateException if the task was already scheduled or 597: * canceled or this Timer is canceled or the scheduler thread has died 598: */ 599: public void scheduleAtFixedRate(TimerTask task, long delay, long period) 600: { 601: positiveDelay(delay); 602: positivePeriod(period); 603: long time = System.currentTimeMillis() + delay; 604: schedule(task, time, period, true); 605: } 606: 607: /** 608: * Tells the scheduler that the Timer task died 609: * so there will be no more new tasks scheduled. 610: */ 611: protected void finalize() throws Throwable 612: { 613: queue.setNullOnEmpty(true); 614: } 615: }
GNU Classpath (0.17) |