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 18 package 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 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 */ 52 public 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 minimum()65 public TimerTask minimum() { 66 return timers[0]; 67 } 68 isEmpty()69 public boolean isEmpty() { 70 return size == 0; 71 } 72 insert(TimerTask task)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 delete(int pos)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 upHeap()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 downHeap(int pos)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 reset()135 public void reset() { 136 timers = new TimerTask[DEFAULT_HEAP_SIZE]; 137 size = 0; 138 } 139 adjustMinimum()140 public void adjustMinimum() { 141 downHeap(0); 142 } 143 deleteIfCancelled()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 getTask(TimerTask task)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 */ TimerImpl(String name, boolean isDaemon)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 run()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 insertTask(TimerTask newTask)296 private void insertTask(TimerTask newTask) { 297 // callers are synchronized 298 tasks.insert(newTask); 299 this.notify(); 300 } 301 302 /** 303 * Cancels timer. 304 */ cancel()305 public synchronized void cancel() { 306 cancelled = true; 307 tasks.reset(); 308 this.notify(); 309 } 310 purge()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 FinalizerHelper(TimerImpl impl)326 FinalizerHelper(TimerImpl impl) { 327 this.impl = impl; 328 } 329 finalize()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 nextId()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 * @param name the name of the {@code Timer}. 360 * @param isDaemon true if {@code Timer}'s thread should be a daemon thread. 361 * @throws NullPointerException is {@code name} is {@code null} 362 */ Timer(String name, boolean isDaemon)363 public Timer(String name, boolean isDaemon) { 364 if (name == null) { 365 throw new NullPointerException("name == null"); 366 } 367 this.impl = new TimerImpl(name, isDaemon); 368 this.finalizer = new FinalizerHelper(impl); 369 } 370 371 /** 372 * Creates a new named {@code Timer} which does not run as a daemon thread. 373 * 374 * @param name the name of the Timer. 375 * @throws NullPointerException is {@code name} is {@code null} 376 */ Timer(String name)377 public Timer(String name) { 378 this(name, false); 379 } 380 381 /** 382 * Creates a new {@code Timer} which may be specified to be run as a daemon thread. 383 * 384 * @param isDaemon {@code true} if the {@code Timer}'s thread should be a daemon thread. 385 */ Timer(boolean isDaemon)386 public Timer(boolean isDaemon) { 387 this("Timer-" + Timer.nextId(), isDaemon); 388 } 389 390 /** 391 * Creates a new non-daemon {@code Timer}. 392 */ Timer()393 public Timer() { 394 this(false); 395 } 396 397 /** 398 * Cancels the {@code Timer} and all scheduled tasks. If there is a 399 * currently running task it is not affected. No more tasks may be scheduled 400 * on this {@code Timer}. Subsequent calls do nothing. 401 */ cancel()402 public void cancel() { 403 impl.cancel(); 404 } 405 406 /** 407 * Removes all canceled tasks from the task queue. If there are no 408 * other references on the tasks, then after this call they are free 409 * to be garbage collected. 410 * 411 * @return the number of canceled tasks that were removed from the task 412 * queue. 413 */ purge()414 public int purge() { 415 synchronized (impl) { 416 return impl.purge(); 417 } 418 } 419 420 /** 421 * Schedule a task for single execution. If {@code when} is less than the 422 * current time, it will be scheduled to be executed as soon as possible. 423 * 424 * @param task 425 * the task to schedule. 426 * @param when 427 * time of execution. 428 * @throws IllegalArgumentException 429 * if {@code when.getTime() < 0}. 430 * @throws IllegalStateException 431 * if the {@code Timer} has been canceled, or if the task has been 432 * scheduled or canceled. 433 */ schedule(TimerTask task, Date when)434 public void schedule(TimerTask task, Date when) { 435 if (when.getTime() < 0) { 436 throw new IllegalArgumentException(); 437 } 438 long delay = when.getTime() - System.currentTimeMillis(); 439 scheduleImpl(task, delay < 0 ? 0 : delay, -1, false); 440 } 441 442 /** 443 * Schedule a task for single execution after a specified delay. 444 * 445 * @param task 446 * the task to schedule. 447 * @param delay 448 * amount of time in milliseconds before execution. 449 * @throws IllegalArgumentException 450 * if {@code delay < 0}. 451 * @throws IllegalStateException 452 * if the {@code Timer} has been canceled, or if the task has been 453 * scheduled or canceled. 454 */ schedule(TimerTask task, long delay)455 public void schedule(TimerTask task, long delay) { 456 if (delay < 0) { 457 throw new IllegalArgumentException(); 458 } 459 scheduleImpl(task, delay, -1, false); 460 } 461 462 /** 463 * Schedule a task for repeated fixed-delay execution after a specific delay. 464 * 465 * @param task 466 * the task to schedule. 467 * @param delay 468 * amount of time in milliseconds before first execution. 469 * @param period 470 * amount of time in milliseconds between subsequent executions. 471 * @throws IllegalArgumentException 472 * if {@code delay < 0} or {@code period <= 0}. 473 * @throws IllegalStateException 474 * if the {@code Timer} has been canceled, or if the task has been 475 * scheduled or canceled. 476 */ schedule(TimerTask task, long delay, long period)477 public void schedule(TimerTask task, long delay, long period) { 478 if (delay < 0 || period <= 0) { 479 throw new IllegalArgumentException(); 480 } 481 scheduleImpl(task, delay, period, false); 482 } 483 484 /** 485 * Schedule a task for repeated fixed-delay execution after a specific time 486 * has been reached. 487 * 488 * @param task 489 * the task to schedule. 490 * @param when 491 * time of first execution. 492 * @param period 493 * amount of time in milliseconds between subsequent executions. 494 * @throws IllegalArgumentException 495 * if {@code when.getTime() < 0} or {@code period <= 0}. 496 * @throws IllegalStateException 497 * if the {@code Timer} has been canceled, or if the task has been 498 * scheduled or canceled. 499 */ schedule(TimerTask task, Date when, long period)500 public void schedule(TimerTask task, Date when, long period) { 501 if (period <= 0 || when.getTime() < 0) { 502 throw new IllegalArgumentException(); 503 } 504 long delay = when.getTime() - System.currentTimeMillis(); 505 scheduleImpl(task, delay < 0 ? 0 : delay, period, false); 506 } 507 508 /** 509 * Schedule a task for repeated fixed-rate execution after a specific delay 510 * has passed. 511 * 512 * @param task 513 * the task to schedule. 514 * @param delay 515 * amount of time in milliseconds before first execution. 516 * @param period 517 * amount of time in milliseconds between subsequent executions. 518 * @throws IllegalArgumentException 519 * if {@code delay < 0} or {@code period <= 0}. 520 * @throws IllegalStateException 521 * if the {@code Timer} has been canceled, or if the task has been 522 * scheduled or canceled. 523 */ scheduleAtFixedRate(TimerTask task, long delay, long period)524 public void scheduleAtFixedRate(TimerTask task, long delay, long period) { 525 if (delay < 0 || period <= 0) { 526 throw new IllegalArgumentException(); 527 } 528 scheduleImpl(task, delay, period, true); 529 } 530 531 /** 532 * Schedule a task for repeated fixed-rate execution after a specific time 533 * has been reached. 534 * 535 * @param task 536 * the task to schedule. 537 * @param when 538 * time of first execution. 539 * @param period 540 * amount of time in milliseconds between subsequent executions. 541 * @throws IllegalArgumentException 542 * if {@code when.getTime() < 0} or {@code period <= 0}. 543 * @throws IllegalStateException 544 * if the {@code Timer} has been canceled, or if the task has been 545 * scheduled or canceled. 546 */ scheduleAtFixedRate(TimerTask task, Date when, long period)547 public void scheduleAtFixedRate(TimerTask task, Date when, long period) { 548 if (period <= 0 || when.getTime() < 0) { 549 throw new IllegalArgumentException(); 550 } 551 long delay = when.getTime() - System.currentTimeMillis(); 552 scheduleImpl(task, delay, period, true); 553 } 554 555 /* 556 * Schedule a task. 557 */ scheduleImpl(TimerTask task, long delay, long period, boolean fixed)558 private void scheduleImpl(TimerTask task, long delay, long period, boolean fixed) { 559 synchronized (impl) { 560 if (impl.cancelled) { 561 throw new IllegalStateException("Timer was canceled"); 562 } 563 564 long when = delay + System.currentTimeMillis(); 565 566 if (when < 0) { 567 throw new IllegalArgumentException("Illegal delay to start the TimerTask: " + when); 568 } 569 570 synchronized (task.lock) { 571 if (task.isScheduled()) { 572 throw new IllegalStateException("TimerTask is scheduled already"); 573 } 574 575 if (task.cancelled) { 576 throw new IllegalStateException("TimerTask is canceled"); 577 } 578 579 task.when = when; 580 task.period = period; 581 task.fixedRate = fixed; 582 } 583 584 // insert the newTask into queue 585 impl.insertTask(task); 586 } 587 } 588 } 589