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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 * Timers schedule one-shot or recurring {@link TimerTask tasks} for execution.
22 * Prefer {@link java.util.concurrent.ScheduledThreadPoolExecutor
23 * ScheduledThreadPoolExecutor} for new code.
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.
28 * <p>One-shot are scheduled to run at an absolute time or after a relative
31 * <p>Recurring tasks are scheduled with either a fixed period or a fixed rate:
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.
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.
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
54 private static final class TimerImpl extends Thread {
56 private static final class TimerHeap {
57 private int DEFAULT_HEAP_SIZE = 256;
59 private TimerTask[] timers = new TimerTask[DEFAULT_HEAP_SIZE];
63 private int deletedCancelledNumber = 0;
65 public TimerTask minimum() {
69 public boolean isEmpty() {
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;
79 timers[size++] = task;
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];
92 private void upHeap() {
93 int current = size - 1;
94 int parent = (current - 1) / 2;
96 while (timers[current].when < timers[parent].when) {
98 TimerTask tmp = timers[current];
99 timers[current] = timers[parent];
100 timers[parent] = tmp;
102 // update pos and current
104 parent = (current - 1) / 2;
108 private void downHeap(int pos) {
110 int child = 2 * current + 1;
112 while (child < size && size > 0) {
113 // compare the children if they exist
115 && timers[child + 1].when < timers[child].when) {
119 // compare selected child with parent
120 if (timers[current].when < timers[child].when) {
125 TimerTask tmp = timers[current];
126 timers[current] = timers[child];
129 // update pos and current
131 child = 2 * current + 1;
135 public void reset() {
136 timers = new TimerTask[DEFAULT_HEAP_SIZE];
140 public void adjustMinimum() {
144 public void deleteIfCancelled() {
145 for (int i = 0; i < size; i++) {
146 if (timers[i].cancelled) {
147 deletedCancelledNumber++;
155 private int getTask(TimerTask task) {
156 for (int i = 0; i < timers.length; i++) {
157 if (timers[i] == task) {
167 * True if the method cancel() of the Timer was called or the !!!stop()
170 private boolean cancelled;
173 * True if the Timer has become garbage
175 private boolean finished;
178 * Vector consists of scheduled events, sorted according to
179 * {@code when} field of TaskScheduled object.
181 private TimerHeap tasks = new TimerHeap();
184 * Starts a new timer.
186 * @param name thread's name
187 * @param isDaemon daemon thread or not
189 TimerImpl(String name, boolean isDaemon) {
191 this.setDaemon(isDaemon);
196 * This method will be launched on separate thread for each Timer
203 synchronized (this) {
204 // need to check cancelled inside the synchronized block
208 if (tasks.isEmpty()) {
212 // no tasks scheduled -- sleep until any task appear
215 } catch (InterruptedException ignored) {
220 long currentTime = System.currentTimeMillis();
222 task = tasks.minimum();
225 synchronized (task.lock) {
226 if (task.cancelled) {
231 // check the time to sleep for the first task scheduled
232 timeToSleep = task.when - currentTime;
235 if (timeToSleep > 0) {
238 this.wait(timeToSleep);
239 } catch (InterruptedException ignored) {
244 // no sleep is necessary before launching the task
246 synchronized (task.lock) {
248 if (tasks.minimum().when != task.when) {
249 pos = tasks.getTask(task);
251 if (task.cancelled) {
252 tasks.delete(tasks.getTask(task));
256 // set time to schedule
257 task.setScheduledTime(task.when);
259 // remove task from queue
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;
269 // task is scheduled at fixed delay
270 task.when = System.currentTimeMillis()
274 // insert this task into queue
282 boolean taskCompletedNormally = false;
285 taskCompletedNormally = true;
287 if (!taskCompletedNormally) {
288 synchronized (this) {
296 private void insertTask(TimerTask newTask) {
297 // callers are synchronized
298 tasks.insert(newTask);
305 public synchronized void cancel() {
312 if (tasks.isEmpty()) {
315 // callers are synchronized
316 tasks.deletedCancelledNumber = 0;
317 tasks.deleteIfCancelled();
318 return tasks.deletedCancelledNumber;
323 private static final class FinalizerHelper {
324 private final TimerImpl impl;
326 FinalizerHelper(TimerImpl impl) {
331 @Override protected void finalize() throws Throwable {
333 synchronized (impl) {
334 impl.finished = true;
343 private static long timerId;
345 private synchronized static long nextId() {
349 /* This object will be used in synchronization purposes */
350 private final TimerImpl impl;
352 // Used to finalize thread
353 @SuppressWarnings("unused")
354 private final FinalizerHelper finalizer;
357 * Creates a new named {@code Timer} which may be specified to be run as a
360 * @param name the name of the {@code Timer}.
361 * @param isDaemon true if {@code Timer}'s thread should be a daemon thread.
362 * @throws NullPointerException is {@code name} is {@code null}
364 public Timer(String name, boolean isDaemon) {
367 throw new NullPointerException("name is null");
369 this.impl = new TimerImpl(name, isDaemon);
370 this.finalizer = new FinalizerHelper(impl);
374 * Creates a new named {@code Timer} which does not run as a daemon thread.
376 * @param name the name of the Timer.
377 * @throws NullPointerException is {@code name} is {@code null}
379 public Timer(String name) {
384 * Creates a new {@code Timer} which may be specified to be run as a daemon thread.
386 * @param isDaemon {@code true} if the {@code Timer}'s thread should be a daemon thread.
388 public Timer(boolean isDaemon) {
389 this("Timer-" + Timer.nextId(), isDaemon);
393 * Creates a new non-daemon {@code Timer}.
400 * Cancels the {@code Timer} and all scheduled tasks. If there is a
401 * currently running task it is not affected. No more tasks may be scheduled
402 * on this {@code Timer}. Subsequent calls do nothing.
404 public void cancel() {
409 * Removes all canceled tasks from the task queue. If there are no
410 * other references on the tasks, then after this call they are free
411 * to be garbage collected.
413 * @return the number of canceled tasks that were removed from the task
417 synchronized (impl) {
423 * Schedule a task for single execution. If {@code when} is less than the
424 * current time, it will be scheduled to be executed as soon as possible.
427 * the task to schedule.
430 * @throws IllegalArgumentException
431 * if {@code when.getTime() < 0}.
432 * @throws IllegalStateException
433 * if the {@code Timer} has been canceled, or if the task has been
434 * scheduled or canceled.
436 public void schedule(TimerTask task, Date when) {
437 if (when.getTime() < 0) {
438 throw new IllegalArgumentException();
440 long delay = when.getTime() - System.currentTimeMillis();
441 scheduleImpl(task, delay < 0 ? 0 : delay, -1, false);
445 * Schedule a task for single execution after a specified delay.
448 * the task to schedule.
450 * amount of time in milliseconds before execution.
451 * @throws IllegalArgumentException
452 * if {@code delay < 0}.
453 * @throws IllegalStateException
454 * if the {@code Timer} has been canceled, or if the task has been
455 * scheduled or canceled.
457 public void schedule(TimerTask task, long delay) {
459 throw new IllegalArgumentException();
461 scheduleImpl(task, delay, -1, false);
465 * Schedule a task for repeated fixed-delay execution after a specific delay.
468 * the task to schedule.
470 * amount of time in milliseconds before first execution.
472 * amount of time in milliseconds between subsequent executions.
473 * @throws IllegalArgumentException
474 * if {@code delay < 0} or {@code period < 0}.
475 * @throws IllegalStateException
476 * if the {@code Timer} has been canceled, or if the task has been
477 * scheduled or canceled.
479 public void schedule(TimerTask task, long delay, long period) {
480 if (delay < 0 || period <= 0) {
481 throw new IllegalArgumentException();
483 scheduleImpl(task, delay, period, false);
487 * Schedule a task for repeated fixed-delay execution after a specific time
491 * the task to schedule.
493 * time of first execution.
495 * amount of time in milliseconds between subsequent executions.
496 * @throws IllegalArgumentException
497 * if {@code when.getTime() < 0} or {@code period < 0}.
498 * @throws IllegalStateException
499 * if the {@code Timer} has been canceled, or if the task has been
500 * scheduled or canceled.
502 public void schedule(TimerTask task, Date when, long period) {
503 if (period <= 0 || when.getTime() < 0) {
504 throw new IllegalArgumentException();
506 long delay = when.getTime() - System.currentTimeMillis();
507 scheduleImpl(task, delay < 0 ? 0 : delay, period, false);
511 * Schedule a task for repeated fixed-rate execution after a specific delay
515 * the task to schedule.
517 * amount of time in milliseconds before first execution.
519 * amount of time in milliseconds between subsequent executions.
520 * @throws IllegalArgumentException
521 * if {@code delay < 0} or {@code period < 0}.
522 * @throws IllegalStateException
523 * if the {@code Timer} has been canceled, or if the task has been
524 * scheduled or canceled.
526 public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
527 if (delay < 0 || period <= 0) {
528 throw new IllegalArgumentException();
530 scheduleImpl(task, delay, period, true);
534 * Schedule a task for repeated fixed-rate execution after a specific time
538 * the task to schedule.
540 * time of first execution.
542 * amount of time in milliseconds between subsequent executions.
543 * @throws IllegalArgumentException
544 * if {@code when.getTime() < 0} or {@code period < 0}.
545 * @throws IllegalStateException
546 * if the {@code Timer} has been canceled, or if the task has been
547 * scheduled or canceled.
549 public void scheduleAtFixedRate(TimerTask task, Date when, long period) {
550 if (period <= 0 || when.getTime() < 0) {
551 throw new IllegalArgumentException();
553 long delay = when.getTime() - System.currentTimeMillis();
554 scheduleImpl(task, delay, period, true);
560 private void scheduleImpl(TimerTask task, long delay, long period, boolean fixed) {
561 synchronized (impl) {
562 if (impl.cancelled) {
563 throw new IllegalStateException("Timer was canceled");
566 long when = delay + System.currentTimeMillis();
569 throw new IllegalArgumentException("Illegal delay to start the TimerTask: " + when);
572 synchronized (task.lock) {
573 if (task.isScheduled()) {
574 throw new IllegalStateException("TimerTask is scheduled already");
577 if (task.cancelled) {
578 throw new IllegalStateException("TimerTask is canceled");
582 task.period = period;
583 task.fixedRate = fixed;
586 // insert the newTask into queue
587 impl.insertTask(task);