OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / libcore / luni / src / main / java / java / util / Timer.java
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
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          * Vector consists of 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             super();
328             this.impl = impl;
329         }
330
331         @Override protected void finalize() throws Throwable {
332             try {
333                 synchronized (impl) {
334                     impl.finished = true;
335                     impl.notify();
336                 }
337             } finally {
338                 super.finalize();
339             }
340         }
341     }
342
343     private static long timerId;
344
345     private synchronized static long nextId() {
346         return timerId++;
347     }
348
349     /* This object will be used in synchronization purposes */
350     private final TimerImpl impl;
351
352     // Used to finalize thread
353     @SuppressWarnings("unused")
354     private final FinalizerHelper finalizer;
355
356     /**
357      * Creates a new named {@code Timer} which may be specified to be run as a
358      * daemon thread.
359      *
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}
363      */
364     public Timer(String name, boolean isDaemon) {
365         super();
366         if (name == null){
367             throw new NullPointerException("name is null");
368         }
369         this.impl = new TimerImpl(name, isDaemon);
370         this.finalizer = new FinalizerHelper(impl);
371     }
372
373     /**
374      * Creates a new named {@code Timer} which does not run as a daemon thread.
375      *
376      * @param name the name of the Timer.
377      * @throws NullPointerException is {@code name} is {@code null}
378      */
379     public Timer(String name) {
380         this(name, false);
381     }
382
383     /**
384      * Creates a new {@code Timer} which may be specified to be run as a daemon thread.
385      *
386      * @param isDaemon {@code true} if the {@code Timer}'s thread should be a daemon thread.
387      */
388     public Timer(boolean isDaemon) {
389         this("Timer-" + Timer.nextId(), isDaemon);
390     }
391
392     /**
393      * Creates a new non-daemon {@code Timer}.
394      */
395     public Timer() {
396         this(false);
397     }
398
399     /**
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.
403      */
404     public void cancel() {
405         impl.cancel();
406     }
407
408     /**
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.
412      *
413      * @return the number of canceled tasks that were removed from the task
414      *         queue.
415      */
416     public int purge() {
417         synchronized (impl) {
418             return impl.purge();
419         }
420     }
421
422     /**
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.
425      *
426      * @param task
427      *            the task to schedule.
428      * @param when
429      *            time of execution.
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.
435      */
436     public void schedule(TimerTask task, Date when) {
437         if (when.getTime() < 0) {
438             throw new IllegalArgumentException();
439         }
440         long delay = when.getTime() - System.currentTimeMillis();
441         scheduleImpl(task, delay < 0 ? 0 : delay, -1, false);
442     }
443
444     /**
445      * Schedule a task for single execution after a specified delay.
446      *
447      * @param task
448      *            the task to schedule.
449      * @param delay
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.
456      */
457     public void schedule(TimerTask task, long delay) {
458         if (delay < 0) {
459             throw new IllegalArgumentException();
460         }
461         scheduleImpl(task, delay, -1, false);
462     }
463
464     /**
465      * Schedule a task for repeated fixed-delay execution after a specific delay.
466      *
467      * @param task
468      *            the task to schedule.
469      * @param delay
470      *            amount of time in milliseconds before first execution.
471      * @param period
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.
478      */
479     public void schedule(TimerTask task, long delay, long period) {
480         if (delay < 0 || period <= 0) {
481             throw new IllegalArgumentException();
482         }
483         scheduleImpl(task, delay, period, false);
484     }
485
486     /**
487      * Schedule a task for repeated fixed-delay execution after a specific time
488      * has been reached.
489      *
490      * @param task
491      *            the task to schedule.
492      * @param when
493      *            time of first execution.
494      * @param period
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.
501      */
502     public void schedule(TimerTask task, Date when, long period) {
503         if (period <= 0 || when.getTime() < 0) {
504             throw new IllegalArgumentException();
505         }
506         long delay = when.getTime() - System.currentTimeMillis();
507         scheduleImpl(task, delay < 0 ? 0 : delay, period, false);
508     }
509
510     /**
511      * Schedule a task for repeated fixed-rate execution after a specific delay
512      * has passed.
513      *
514      * @param task
515      *            the task to schedule.
516      * @param delay
517      *            amount of time in milliseconds before first execution.
518      * @param period
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.
525      */
526     public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
527         if (delay < 0 || period <= 0) {
528             throw new IllegalArgumentException();
529         }
530         scheduleImpl(task, delay, period, true);
531     }
532
533     /**
534      * Schedule a task for repeated fixed-rate execution after a specific time
535      * has been reached.
536      *
537      * @param task
538      *            the task to schedule.
539      * @param when
540      *            time of first execution.
541      * @param period
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.
548      */
549     public void scheduleAtFixedRate(TimerTask task, Date when, long period) {
550         if (period <= 0 || when.getTime() < 0) {
551             throw new IllegalArgumentException();
552         }
553         long delay = when.getTime() - System.currentTimeMillis();
554         scheduleImpl(task, delay, period, true);
555     }
556
557     /*
558      * Schedule a task.
559      */
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");
564             }
565
566             long when = delay + System.currentTimeMillis();
567
568             if (when < 0) {
569                 throw new IllegalArgumentException("Illegal delay to start the TimerTask: " + when);
570             }
571
572             synchronized (task.lock) {
573                 if (task.isScheduled()) {
574                     throw new IllegalStateException("TimerTask is scheduled already");
575                 }
576
577                 if (task.cancelled) {
578                     throw new IllegalStateException("TimerTask is canceled");
579                 }
580
581                 task.when = when;
582                 task.period = period;
583                 task.fixedRate = fixed;
584             }
585
586             // insert the newTask into queue
587             impl.insertTask(task);
588         }
589     }
590 }