Java里的Timer源码分析

简介:

Java Timer class is thread safe and multiple threads can share a single Timer object without need for external synchronization. Timer class uses java.util.TaskQueue to add tasks at given regular interval and at any time there can be only one thread running the TimerTask, for example if you are creating a Timer to run every 10 seconds but single thread execution takes 20 seconds, then Timer object will keep adding tasks to the queue and as soon as one thread is finished, it will notify the queue and another thread will start executing.

Java Timer class uses Object wait and notify methods to schedule the tasks.

要点:

1. Timer中的schedule功能提供了一个添加任务定时执行的功能,他支持多线程。这个功能的实现概括来讲就是用一个优先队列,建立任务(Task),把任务按实行时间加到优先队列里。睡若干秒直到第一个任务。

2. Timer用一个TaskQueue,就是一个优先队列,可以把任务(Task) 按实行时间加到优先队列里。执行的时候,只有一个线程可以进行操作。提到了一个例子,就是如果一个任务间隔10s,但是执行花20s,还是得等执行完以后,新的任务才能添加。(这时加入队列的时间会为负数,也就是说立即实行)

3.运用java多线程里的wait和notify。

4. 对于设计添加任务定时执行的功能的系统设计,一个好的切入点就是先从单线程/多线程开始,拓展到多机器。任务定时的单线程版本最简单流行的实现就是Java里Timer中的schedule功能。所以正好对应这个题目。

源码分析

1. Timer

1)变量

Timer class 的结构很清晰,只有两个变量。一个TaskQueue,一个TimerThread。TimerThread是一个有TaskQueue实例的线程。

 1 /**
 2      * The timer task queue.  This data structure is shared with the timer
 3      * thread.  The timer produces tasks, via its various schedule calls,
 4      * and the timer thread consumes, executing timer tasks as appropriate,
 5      * and removing them from the queue when they're obsolete.
 6      */
 7     private final TaskQueue queue = new TaskQueue();
 8 
 9     /**
10      * The timer thread.
11      */
12     private final TimerThread thread = new TimerThread(queue);

2) constructor

4种构造方法分别对应timer的线程是否是守护线程,线程是否有名字。

     * @param name the name of the associated thread

     * @param isDaemon true if the associated thread should run as a daemon

 1     public Timer() {
 2         this("Timer-" + serialNumber());
 3     }
 4 
 5     public Timer(boolean isDaemon) {
 6         this("Timer-" + serialNumber(), isDaemon);
 7     }
 8 
 9     public Timer(String name) {
10         thread.setName(name);
11         thread.start();
12     }
13

14 public Timer(String name, boolean isDaemon) {
15         thread.setName(name);
16         thread.setDaemon(isDaemon);
17         thread.start();
18     }

3)schedule方法:

一共有6种public的schedule方法,第1,2种为执行一次。剩下4种为定期执行,其中根据对延期的不用处理,分为间隔优先(fix-delay) / 时间优先(fix-rate)。

这6种schedule方法都call同一个private的sched方法 - sched(TimerTask task, long time, long period),区别是执行一次的period变量设为0,间隔优先设为period的负数,时间优先设为period本身。这样不用多传一个执行类型的变量了。

 1 //  1.    执行一次
 2     /**
 3      * Schedules the specified task for execution after the specified delay.
 4      */
 5     public void schedule(TimerTask task, long delay) {
 6         if (delay < 0)
 7             throw new IllegalArgumentException("Negative delay.");
 8         sched(task, System.currentTimeMillis()+delay, 0);
 9     }
10 
11     /**
12      * Schedules the specified task for execution at the specified time.  If
13      * the time is in the past, the task is scheduled for immediate execution.
14      */
15     public void schedule(TimerTask task, Date time) {
16         sched(task, time.getTime(), 0);
17     }
18    // 2. 定时执行 -  fixed-delay 策略 适用于要求间隔尽量一致,而不是必须某时间执行的需求
19     /**
20      * Schedules the specified task for repeated 
21      * <p>In fixed-delay execution, each execution is scheduled relative to
22      * the actual execution time of the previous execution.  If an execution
23      * is delayed for any reason (such as garbage collection or other
24      * background activity), subsequent executions will be delayed as well.
25      * In the long run, the frequency of execution will generally be slightly
26      * lower than the reciprocal of the specified period (assuming the system
27      * clock underlying */
29     public void schedule(TimerTask task, long delay, long period) {
30         if (delay < 0)
31             throw new IllegalArgumentException("Negative delay.");
32         if (period <= 0)
33             throw new IllegalArgumentException("Non-positive period.");
34         sched(task, System.currentTimeMillis()+delay, -period);
35     }
36 
37     public void schedule(TimerTask task, Date firstTime, long period) {
38         if (period <= 0)
39             throw new IllegalArgumentException("Non-positive period.");
40         sched(task, firstTime.getTime(), -period);
41     }
42    3.  定时执行 - fixed-rate 策略 适用于必须某时间执行的需求
43     /**
44      * Schedules the specified task for repeated <i>fixed-rate execution</i>,
45      * beginning after the specified delay.  Subsequent executions take place
46      * at approximately regular intervals, separated by the specified period.
47      *
48      * In fixed-rate execution, each execution is scheduled relative to the
49      * scheduled execution time of the initial execution.  If an execution is
50      * delayed for any reason (such as garbage collection or other background
51      * activity), two or more executions will occur in rapid succession to
52      * "catch up."  In the long run, the frequency of execution will be
53      * exactly the reciprocal of the specified period (assuming the system
54      * clock underlying 
55      */
56     public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
57         if (delay < 0)
58             throw new IllegalArgumentException("Negative delay.");
59         if (period <= 0)
60             throw new IllegalArgumentException("Non-positive period.");
61         sched(task, System.currentTimeMillis()+delay, period);
62     }
63 
64     public void scheduleAtFixedRate(TimerTask task, Date firstTime,
65                                     long period) {
66         if (period <= 0)
67             throw new IllegalArgumentException("Non-positive period.");
68         sched(task, firstTime.getTime(), period);
69     }

 再来看call的这个方法,基本上就是把可执行的task放入优先队列,过程中分别对优先队列和task加锁。

 1 /**
 2      * Schedule the specified timer task for execution at the specified
 3      * time with the specified period, in milliseconds.  If period is
 4      * positive, the task is scheduled for repeated execution; if period is
 5      * zero, the task is scheduled for one-time execution. Time is specified
 6      * in Date.getTime() format.  This method checks timer state, task state,
 7      * and initial execution time, but not period.
 8    */
 9     private void sched(TimerTask task, long time, long period) {
10         if (time < 0)
11             throw new IllegalArgumentException("Illegal execution time.");
12 
13         // Constrain value of period sufficiently to prevent numeric
14         // overflow while still being effectively infinitely large.
15         if (Math.abs(period) > (Long.MAX_VALUE >> 1))
16             period >>= 1;
17 
18         synchronized(queue) {
19             if (!thread.newTasksMayBeScheduled)
20                 throw new IllegalStateException("Timer already cancelled.");
21 
22             synchronized(task.lock) {
23                 if (task.state != TimerTask.VIRGIN)
24                     throw new IllegalStateException(
25                         "Task already scheduled or cancelled");
26                 task.nextExecutionTime = time;
27                 task.period = period;
28                 task.state = TimerTask.SCHEDULED; // 加入队列,状态改为SCHEDULED
29             }
30 
31             queue.add(task);
32             if (queue.getMin() == task)
33                 queue.notify(); // 如果新放入的task的执行时间是最近的,唤醒优先队列。因为要更新等待时间。
34         }
35     }
 1     /**
 2      * Adds a new task to the priority queue.
 3      */
 4     void add(TimerTask task) {
 5         // Grow backing store if necessary
 6         if (size + 1 == queue.length)
 7             queue = Arrays.copyOf(queue, 2*queue.length);
 8 
 9         queue[++size] = task;
10         fixUp(size);
11     }

2. TimerThread 

TimerThread类的变量和constructor 主要就是优先队列和一个有无任务的flag。

这里有一个点是TaskQueue这个变量在TimerThread, Timer中是共享的。这样Timer有TimerThread,TaskQueue的reference,TimerThread 有TaskQueue 的reference,可以让Timer进行garbage-collection。

 1     /**
 2      * This flag is set to false by the reaper to inform us that there
 3      * are no more live references to our Timer object.  Once this flag
 4      * is true and there are no more tasks in our queue, there is no
 5      * work left for us to do, so we terminate gracefully.  Note that
 6      * this field is protected by queue's monitor!
 7      */
 8     boolean newTasksMayBeScheduled = true;
 9 
10     /**
11      * Our Timer's queue.  We store this reference in preference to
12      * a reference to the Timer so the reference graph remains acyclic.
13      * Otherwise, the Timer would never be garbage-collected and this
14      * thread would never go away.
15      */
16     private TaskQueue queue;
17 
18     TimerThread(TaskQueue queue) {
19         this.queue = queue;
20     }

TimerThread里的run 方法,一个直到优先队列为空且newTaskMayBeScheduled为否跳出的无限循环,一开始一直等待直到有元素加入,如果优先队列有元素,等待若干秒直到第一个任务的执行时间,执行任务,如果是多次执行的任务,计算下个执行时间加入队列。

 1     public void run() {
 2         try {
 3             mainLoop();
 4         } finally {
 5             // Someone killed this Thread, behave as if Timer cancelled
 6             synchronized(queue) {
 7                 newTasksMayBeScheduled = false;
 8                 queue.clear();  // Eliminate obsolete references
 9             }
10         }
11     }
 1 private void mainLoop() {
 2         while (true) {
 3             try {
 4                 TimerTask task;
 5                 boolean taskFired;
 6                 synchronized(queue) {
 7                     // Wait for queue to become non-empty
 8                     while (queue.isEmpty() && newTasksMayBeScheduled)
 9                         queue.wait();
10                     if (queue.isEmpty())
11                         break; // Queue is empty and will forever remain; die
12 
13                     // Queue nonempty; look at first evt and do the right thing
14                     long currentTime, executionTime;
15                     task = queue.getMin();
16                     synchronized(task.lock) {
17                         if (task.state == TimerTask.CANCELLED) {
18                             queue.removeMin();
19                             continue;  // No action required, poll queue again
20                         }
21                         currentTime = System.currentTimeMillis();
22                         executionTime = task.nextExecutionTime;
23                         if (taskFired = (executionTime<=currentTime)) {//最近的执行时间小于当前时间,set taskFired为true
24                             if (task.period == 0) { // 一次执行 Non-repeating, remove
25                                 queue.removeMin();
26                                 task.state = TimerTask.EXECUTED;
27                             } else { // Repeating task, reschedule 多次执行 
28                                         // period小于0: 下次执行时间基于当前时间。period大于0: 下次执行时间基于这次执行时间。
29                                 queue.rescheduleMin(
30                                   task.period<0 ? currentTime   - task.period
31                                                 : executionTime + task.period);
32                             }
33                         }
34                     }
35                     if (!taskFired) // Task hasn't yet fired; wait
36                         queue.wait(executionTime - currentTime);
37                 }
38                 if (taskFired)  // Task fired; run it, holding no locks
39                     task.run();
40             } catch(InterruptedException e) {
41             }
42         }
43     }

3. TaskQueue

TaskQueue 就是一个TimerTask作为内容,nextExecutionTime为排序依据的priorityQueue(在注解中叫做balanced binary heap)。他的实现是一个1为base的,初始大小为128的TimerTask 数组。

 1 /**
 2      * Priority queue represented as a balanced binary heap: the two children
 3      * of queue[n] are queue[2*n] and queue[2*n+1].  The priority queue is
 4      * ordered on the nextExecutionTime field: The TimerTask with the lowest
 5      * nextExecutionTime is in queue[1] (assuming the queue is nonempty).  For
 6      * each node n in the heap, and each descendant of n, d,
 7      * n.nextExecutionTime <= d.nextExecutionTime.
 8      */
 9     private TimerTask[] queue = new TimerTask[128];
10 
11     /**
12      * The number of tasks in the priority queue.  (The tasks are stored in
13      * queue[1] up to queue[size]).
14      */
15     private int size = 0;

接下来就是standard的heap里的方法。

 1 /**
 2      * Sets the nextExecutionTime associated with the head task to the
 3      * specified value, and adjusts priority queue accordingly.
 4      */
 5     void rescheduleMin(long newTime) { // reset队列顶的重复任务的执行时间,再放到相应的位置
 6         queue[1].nextExecutionTime = newTime;
 7         fixDown(1);
 8     }
 9 
10 /**
11      * Establishes the heap invariant (described above) assuming the heap
12      * satisfies the invariant except possibly for the leaf-node indexed by k
13      * (which may have a nextExecutionTime less than its parent's).
14      *
15      * This method functions by "promoting" queue[k] up the hierarchy
16      * (by swapping it with its parent) repeatedly until queue[k]'s
17      * nextExecutionTime is greater than or equal to that of its parent.
18      */
19     private void fixUp(int k) { // addTask 方法:把新的task加入arr最后,然后以size为参数call这个fixUp方法,这样新加入的task按时间排到相应的位置。
20         while (k > 1) {
21             int j = k >> 1;
22             if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
23                 break;
24             TimerTask tmp = queue[j];  
queue[j] = queue[k];
queue[k] = tmp; 25 k = j; 26 } 27 } 28 29 /** 30 * Establishes the heap invariant (described above) in the subtree 31 * rooted at k, which is assumed to satisfy the heap invariant except 32 * possibly for node k itself (which may have a nextExecutionTime greater 33 * than its children's). 34 * 35 * This method functions by "demoting" queue[k] down the hierarchy 36 * (by swapping it with its smaller child) repeatedly until queue[k]'s 37 * nextExecutionTime is less than or equal to those of its children. 38 */ 39 private void fixDown(int k) { 40 int j; 41 while ((j = k << 1) <= size && j > 0) { 42 if (j < size && queue[j].nextExecutionTime > queue[j+1].nextExecutionTime) 44 j++; // j indexes smallest kid 45 if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime) 46 break; 47 TimerTask tmp = queue[j]; // 与小的儿子交换
queue[j] = queue[k];
queue[k] = tmp; 48 k = j; 49 } 50 } 51 52 /** 53 * Establishes the heap invariant (described above) in the entire tree, 54 * assuming nothing about the order of the elements prior to the call. 55 */ 56 void heapify() { // purge方法调用heapify重新排序。 57 for (int i = size/2; i >= 1; i--) 58 fixDown(i); 59 }

4. 其他:

Timer类中,还包括方法如cancel,purge。

 1    /**
 2      * Terminates this timer, discarding any currently scheduled tasks.
 3      * Does not interfere with a currently executing task (if it exists).
 4      * Once a timer has been terminated, its execution thread terminates
 5      * gracefully, and no more tasks may be scheduled on it.
 6      *
 7      */
 8     public void cancel() {
 9         synchronized(queue) {
10             thread.newTasksMayBeScheduled = false;
11             queue.clear();
12             queue.notify();  // In case queue was already empty.
13         }
14     }
15 
16   /**
17      * Removes all cancelled tasks from this timer's task queue.  <i>Calling
18      * this method has no effect on the behavior of the timer</i>, but
19      * eliminates the references to the cancelled tasks from the queue.
20      * If there are no external references to these tasks, they become
21      * eligible for garbage collection.
22      *
23      * <p>Most programs will have no need to call this method.
24      * It is designed for use by the rare application that cancels a large
25      * number of tasks.  Calling this method trades time for space: the
26      * runtime of the method may be proportional to n + c log n, where n
27      * is the number of tasks in the queue and c is the number of cancelled
28      * tasks.
29      */
30      public int purge() {
31          int result = 0;
32 
33          synchronized(queue) {
34              for (int i = queue.size(); i > 0; i--) {
35                  if (queue.get(i).state == TimerTask.CANCELLED) {
36                      queue.quickRemove(i);
37                      result++;
38                  }
39              }
40 
41              if (result != 0)
42                  queue.heapify();
43          }
44 
45          return result;
46      }

reference:

https://www.journaldev.com/1050/java-timer-timertask-example

猜你喜欢

转载自www.cnblogs.com/ylzylz/p/10689178.html