1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.util; 19 20/** 21 * Timers schedule one-shot or recurring {@link TimerTask tasks} for execution. 22 * Prefer {@link java.util.concurrent.ScheduledThreadPoolExecutor 23 * ScheduledThreadPoolExecutor} for new code. 24 * 25 * <p>Each timer has one thread on which tasks are executed sequentially. When 26 * this thread is busy running a task, runnable tasks may be subject to delays. 27 * 28 * <p>One-shot tasks are scheduled to run at an absolute time or after a relative 29 * delay. 30 * 31 * <p>Recurring tasks are scheduled with either a fixed period or a fixed rate: 32 * <ul> 33 * <li>With the default <strong>fixed-period execution</strong>, each 34 * successive run of a task is scheduled relative to the start time of 35 * the previous run, so two runs are never fired closer together in time 36 * than the specified {@code period}. 37 * <li>With <strong>fixed-rate execution</strong>, the start time of each 38 * successive run of a task is scheduled without regard for when the 39 * previous run took place. This may result in a series of bunched-up runs 40 * (one launched immediately after another) if delays prevent the timer 41 * from starting tasks on time. 42 * </ul> 43 * 44 * <p>When a timer is no longer needed, users should call {@link #cancel}, which 45 * releases the timer's thread and other resources. Timers not explicitly 46 * cancelled may hold resources indefinitely. 47 * 48 * <p>This class does not offer guarantees about the real-time nature of task 49 * scheduling. Multiple threads can share a single timer without 50 * synchronization. 51 */ 52public class Timer { 53 54 private static final class TimerImpl extends Thread { 55 56 private static final class TimerHeap { 57 private int DEFAULT_HEAP_SIZE = 256; 58 59 private TimerTask[] timers = new TimerTask[DEFAULT_HEAP_SIZE]; 60 61 private int size = 0; 62 63 private int deletedCancelledNumber = 0; 64 65 public TimerTask minimum() { 66 return timers[0]; 67 } 68 69 public boolean isEmpty() { 70 return size == 0; 71 } 72 73 public void insert(TimerTask task) { 74 if (timers.length == size) { 75 TimerTask[] appendedTimers = new TimerTask[size * 2]; 76 System.arraycopy(timers, 0, appendedTimers, 0, size); 77 timers = appendedTimers; 78 } 79 timers[size++] = task; 80 upHeap(); 81 } 82 83 public void delete(int pos) { 84 // posible to delete any position of the heap 85 if (pos >= 0 && pos < size) { 86 timers[pos] = timers[--size]; 87 timers[size] = null; 88 downHeap(pos); 89 } 90 } 91 92 private void upHeap() { 93 int current = size - 1; 94 int parent = (current - 1) / 2; 95 96 while (timers[current].when < timers[parent].when) { 97 // swap the two 98 TimerTask tmp = timers[current]; 99 timers[current] = timers[parent]; 100 timers[parent] = tmp; 101 102 // update pos and current 103 current = parent; 104 parent = (current - 1) / 2; 105 } 106 } 107 108 private void downHeap(int pos) { 109 int current = pos; 110 int child = 2 * current + 1; 111 112 while (child < size && size > 0) { 113 // compare the children if they exist 114 if (child + 1 < size 115 && timers[child + 1].when < timers[child].when) { 116 child++; 117 } 118 119 // compare selected child with parent 120 if (timers[current].when < timers[child].when) { 121 break; 122 } 123 124 // swap the two 125 TimerTask tmp = timers[current]; 126 timers[current] = timers[child]; 127 timers[child] = tmp; 128 129 // update pos and current 130 current = child; 131 child = 2 * current + 1; 132 } 133 } 134 135 public void reset() { 136 timers = new TimerTask[DEFAULT_HEAP_SIZE]; 137 size = 0; 138 } 139 140 public void adjustMinimum() { 141 downHeap(0); 142 } 143 144 public void deleteIfCancelled() { 145 for (int i = 0; i < size; i++) { 146 if (timers[i].cancelled) { 147 deletedCancelledNumber++; 148 delete(i); 149 // re-try this point 150 i--; 151 } 152 } 153 } 154 155 private int getTask(TimerTask task) { 156 for (int i = 0; i < timers.length; i++) { 157 if (timers[i] == task) { 158 return i; 159 } 160 } 161 return -1; 162 } 163 164 } 165 166 /** 167 * True if the method cancel() of the Timer was called or the !!!stop() 168 * method was invoked 169 */ 170 private boolean cancelled; 171 172 /** 173 * True if the Timer has become garbage 174 */ 175 private boolean finished; 176 177 /** 178 * Contains scheduled events, sorted according to 179 * {@code when} field of TaskScheduled object. 180 */ 181 private TimerHeap tasks = new TimerHeap(); 182 183 /** 184 * Starts a new timer. 185 * 186 * @param name thread's name 187 * @param isDaemon daemon thread or not 188 */ 189 TimerImpl(String name, boolean isDaemon) { 190 this.setName(name); 191 this.setDaemon(isDaemon); 192 this.start(); 193 } 194 195 /** 196 * This method will be launched on separate thread for each Timer 197 * object. 198 */ 199 @Override 200 public void run() { 201 while (true) { 202 TimerTask task; 203 synchronized (this) { 204 // need to check cancelled inside the synchronized block 205 if (cancelled) { 206 return; 207 } 208 if (tasks.isEmpty()) { 209 if (finished) { 210 return; 211 } 212 // no tasks scheduled -- sleep until any task appear 213 try { 214 this.wait(); 215 } catch (InterruptedException ignored) { 216 } 217 continue; 218 } 219 220 long currentTime = System.currentTimeMillis(); 221 222 task = tasks.minimum(); 223 long timeToSleep; 224 225 synchronized (task.lock) { 226 if (task.cancelled) { 227 tasks.delete(0); 228 continue; 229 } 230 231 // check the time to sleep for the first task scheduled 232 timeToSleep = task.when - currentTime; 233 } 234 235 if (timeToSleep > 0) { 236 // sleep! 237 try { 238 this.wait(timeToSleep); 239 } catch (InterruptedException ignored) { 240 } 241 continue; 242 } 243 244 // no sleep is necessary before launching the task 245 246 synchronized (task.lock) { 247 int pos = 0; 248 if (tasks.minimum().when != task.when) { 249 pos = tasks.getTask(task); 250 } 251 if (task.cancelled) { 252 tasks.delete(tasks.getTask(task)); 253 continue; 254 } 255 256 // set time to schedule 257 task.setScheduledTime(task.when); 258 259 // remove task from queue 260 tasks.delete(pos); 261 262 // set when the next task should be launched 263 if (task.period >= 0) { 264 // this is a repeating task, 265 if (task.fixedRate) { 266 // task is scheduled at fixed rate 267 task.when = task.when + task.period; 268 } else { 269 // task is scheduled at fixed delay 270 task.when = System.currentTimeMillis() 271 + task.period; 272 } 273 274 // insert this task into queue 275 insertTask(task); 276 } else { 277 task.when = 0; 278 } 279 } 280 } 281 282 boolean taskCompletedNormally = false; 283 try { 284 task.run(); 285 taskCompletedNormally = true; 286 } finally { 287 if (!taskCompletedNormally) { 288 synchronized (this) { 289 cancelled = true; 290 } 291 } 292 } 293 } 294 } 295 296 private void insertTask(TimerTask newTask) { 297 // callers are synchronized 298 tasks.insert(newTask); 299 this.notify(); 300 } 301 302 /** 303 * Cancels timer. 304 */ 305 public synchronized void cancel() { 306 cancelled = true; 307 tasks.reset(); 308 this.notify(); 309 } 310 311 public int purge() { 312 if (tasks.isEmpty()) { 313 return 0; 314 } 315 // callers are synchronized 316 tasks.deletedCancelledNumber = 0; 317 tasks.deleteIfCancelled(); 318 return tasks.deletedCancelledNumber; 319 } 320 321 } 322 323 private static final class FinalizerHelper { 324 private final TimerImpl impl; 325 326 FinalizerHelper(TimerImpl impl) { 327 this.impl = impl; 328 } 329 330 @Override protected void finalize() throws Throwable { 331 try { 332 synchronized (impl) { 333 impl.finished = true; 334 impl.notify(); 335 } 336 } finally { 337 super.finalize(); 338 } 339 } 340 } 341 342 private static long timerId; 343 344 private synchronized static long nextId() { 345 return timerId++; 346 } 347 348 /* This object will be used in synchronization purposes */ 349 private final TimerImpl impl; 350 351 // Used to finalize thread 352 @SuppressWarnings("unused") 353 private final FinalizerHelper finalizer; 354 355 /** 356 * Creates a new named {@code Timer} which may be specified to be run as a 357 * daemon thread. 358 * 359 * @throws NullPointerException if {@code name == null} 360 */ 361 public Timer(String name, boolean isDaemon) { 362 if (name == null) { 363 throw new NullPointerException("name == null"); 364 } 365 this.impl = new TimerImpl(name, isDaemon); 366 this.finalizer = new FinalizerHelper(impl); 367 } 368 369 /** 370 * Creates a new named {@code Timer} which does not run as a daemon thread. 371 * 372 * @throws NullPointerException if {@code name == null} 373 */ 374 public Timer(String name) { 375 this(name, false); 376 } 377 378 /** 379 * Creates a new {@code Timer} which may be specified to be run as a daemon thread. 380 * 381 * @param isDaemon {@code true} if the {@code Timer}'s thread should be a daemon thread. 382 */ 383 public Timer(boolean isDaemon) { 384 this("Timer-" + Timer.nextId(), isDaemon); 385 } 386 387 /** 388 * Creates a new non-daemon {@code Timer}. 389 */ 390 public Timer() { 391 this(false); 392 } 393 394 /** 395 * Cancels the {@code Timer} and all scheduled tasks. If there is a 396 * currently running task it is not affected. No more tasks may be scheduled 397 * on this {@code Timer}. Subsequent calls do nothing. 398 */ 399 public void cancel() { 400 impl.cancel(); 401 } 402 403 /** 404 * Removes all canceled tasks from the task queue. If there are no 405 * other references on the tasks, then after this call they are free 406 * to be garbage collected. 407 * 408 * @return the number of canceled tasks that were removed from the task 409 * queue. 410 */ 411 public int purge() { 412 synchronized (impl) { 413 return impl.purge(); 414 } 415 } 416 417 /** 418 * Schedule a task for single execution. If {@code when} is less than the 419 * current time, it will be scheduled to be executed as soon as possible. 420 * 421 * @param task 422 * the task to schedule. 423 * @param when 424 * time of execution. 425 * @throws IllegalArgumentException 426 * if {@code when.getTime() < 0}. 427 * @throws IllegalStateException 428 * if the {@code Timer} has been canceled, or if the task has been 429 * scheduled or canceled. 430 */ 431 public void schedule(TimerTask task, Date when) { 432 if (when.getTime() < 0) { 433 throw new IllegalArgumentException("when < 0: " + when.getTime()); 434 } 435 long delay = when.getTime() - System.currentTimeMillis(); 436 scheduleImpl(task, delay < 0 ? 0 : delay, -1, false); 437 } 438 439 /** 440 * Schedule a task for single execution after a specified delay. 441 * 442 * @param task 443 * the task to schedule. 444 * @param delay 445 * amount of time in milliseconds before execution. 446 * @throws IllegalArgumentException 447 * if {@code delay < 0}. 448 * @throws IllegalStateException 449 * if the {@code Timer} has been canceled, or if the task has been 450 * scheduled or canceled. 451 */ 452 public void schedule(TimerTask task, long delay) { 453 if (delay < 0) { 454 throw new IllegalArgumentException("delay < 0: " + delay); 455 } 456 scheduleImpl(task, delay, -1, false); 457 } 458 459 /** 460 * Schedule a task for repeated fixed-delay execution after a specific delay. 461 * 462 * @param task 463 * the task to schedule. 464 * @param delay 465 * amount of time in milliseconds before first execution. 466 * @param period 467 * amount of time in milliseconds between subsequent executions. 468 * @throws IllegalArgumentException 469 * if {@code delay < 0} or {@code period <= 0}. 470 * @throws IllegalStateException 471 * if the {@code Timer} has been canceled, or if the task has been 472 * scheduled or canceled. 473 */ 474 public void schedule(TimerTask task, long delay, long period) { 475 if (delay < 0 || period <= 0) { 476 throw new IllegalArgumentException(); 477 } 478 scheduleImpl(task, delay, period, false); 479 } 480 481 /** 482 * Schedule a task for repeated fixed-delay execution after a specific time 483 * has been reached. 484 * 485 * @param task 486 * the task to schedule. 487 * @param when 488 * time of first execution. 489 * @param period 490 * amount of time in milliseconds between subsequent executions. 491 * @throws IllegalArgumentException 492 * if {@code when.getTime() < 0} or {@code period <= 0}. 493 * @throws IllegalStateException 494 * if the {@code Timer} has been canceled, or if the task has been 495 * scheduled or canceled. 496 */ 497 public void schedule(TimerTask task, Date when, long period) { 498 if (period <= 0 || when.getTime() < 0) { 499 throw new IllegalArgumentException(); 500 } 501 long delay = when.getTime() - System.currentTimeMillis(); 502 scheduleImpl(task, delay < 0 ? 0 : delay, period, false); 503 } 504 505 /** 506 * Schedule a task for repeated fixed-rate execution after a specific delay 507 * has passed. 508 * 509 * @param task 510 * the task to schedule. 511 * @param delay 512 * amount of time in milliseconds before first execution. 513 * @param period 514 * amount of time in milliseconds between subsequent executions. 515 * @throws IllegalArgumentException 516 * if {@code delay < 0} or {@code period <= 0}. 517 * @throws IllegalStateException 518 * if the {@code Timer} has been canceled, or if the task has been 519 * scheduled or canceled. 520 */ 521 public void scheduleAtFixedRate(TimerTask task, long delay, long period) { 522 if (delay < 0 || period <= 0) { 523 throw new IllegalArgumentException(); 524 } 525 scheduleImpl(task, delay, period, true); 526 } 527 528 /** 529 * Schedule a task for repeated fixed-rate execution after a specific time 530 * has been reached. 531 * 532 * @param task 533 * the task to schedule. 534 * @param when 535 * time of first execution. 536 * @param period 537 * amount of time in milliseconds between subsequent executions. 538 * @throws IllegalArgumentException 539 * if {@code when.getTime() < 0} or {@code period <= 0}. 540 * @throws IllegalStateException 541 * if the {@code Timer} has been canceled, or if the task has been 542 * scheduled or canceled. 543 */ 544 public void scheduleAtFixedRate(TimerTask task, Date when, long period) { 545 if (period <= 0 || when.getTime() < 0) { 546 throw new IllegalArgumentException(); 547 } 548 long delay = when.getTime() - System.currentTimeMillis(); 549 scheduleImpl(task, delay, period, true); 550 } 551 552 /* 553 * Schedule a task. 554 */ 555 private void scheduleImpl(TimerTask task, long delay, long period, boolean fixed) { 556 synchronized (impl) { 557 if (impl.cancelled) { 558 throw new IllegalStateException("Timer was canceled"); 559 } 560 561 long when = delay + System.currentTimeMillis(); 562 563 if (when < 0) { 564 throw new IllegalArgumentException("Illegal delay to start the TimerTask: " + when); 565 } 566 567 synchronized (task.lock) { 568 if (task.isScheduled()) { 569 throw new IllegalStateException("TimerTask is scheduled already"); 570 } 571 572 if (task.cancelled) { 573 throw new IllegalStateException("TimerTask is canceled"); 574 } 575 576 task.when = when; 577 task.period = period; 578 task.fixedRate = fixed; 579 } 580 581 // insert the newTask into queue 582 impl.insertTask(task); 583 } 584 } 585} 586