读书人

java.util.Timer分析源码理解原理

发布时间: 2012-12-23 11:28:15 作者: rapoo

java.util.Timer分析源码了解原理

Timer中最主要由三个部分组成:?任务 TimerTask 、??任务队列:?TaskQueue queue?和?任务调试者:TimerThread thread

他们之间的关系可以通过下面图示:

java.util.Timer分析源码理解原理

在这个图中,可以清楚地看到这Timer本身及其和这三个部分的关系:

1. Timer可以看作是面向开发人员的一个"接口"

2. 所有向Timer添加的任务都会被放入一个TaskQueue类型的任务队列中去.(如何安排任务优先级顺序下文会讲)

3. 任务调度由TimerThread负责

任务单元 TimerTask

?首先看一下任务单元实体类: TimerTask.

?在这个类中, 要关注的是任务状态和几个状态常量:

[移除任务]

void removeMin() {queue[1] = queue[size];queue[size--] = null; // Drop extra reference to prevent memory leakfixDown(1);}

从任务队列中移除一个任务的过程, 首先直接将当前任务队列中最后一个任务赋给queue[1], 然后将队列中任务数量--, 最后和上面类似, 但是这里是调用fixDown(int k)方法了, 尽量将k位置的任务向队列后面移动.

/** * -将k位置的元素向堆底方向移动.<br> * 1. j = k << 1, 将j定位到儿子中.<br> * 2. 将 j 精确定位到较小的儿子.<br> * 3. 然后k与j比较,如果k大于j的话, 那么互换<br> * 4.继续... */private void fixDown(int k) {int j;// 如果还没有到队列的最后,并且没有溢出( j > 0 )// 在没有出现溢出的情况下, j = k << 1 等价于 j = 2 * k ;while ((j = k << 1) <= size && j > 0) {// 找到k的两个孩子中小的那个.if (j < size && queue[j].nextExecutionTime > queue[j + 1].nextExecutionTime)j++; // j indexes smallest kid// 找到这个较小的孩子后,(此时k是父亲,j是较小的儿子),父亲和儿子互换位置,即k和j换位子.这样一直下去就可以将这个较大的queue[1]向下堆底移动了.if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)break;TimerTask tmp = queue[j];queue[j] = queue[k];queue[k] = tmp;k = j;}}

下面来看看任务调度者是如何工作的.

任务调度 TimerThread

关于任务调度主要要讲下一个成员变量 newTasksMayBeScheduled 和 调度方法 mainLoop().

private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired = false;synchronized (queue) {while (queue.isEmpty() && newTasksMayBeScheduled) {queue.wait();}if (queue.isEmpty())break; // 直接挑出mainLoop了.long currentTime, executionTime;task = queue.getMin(); // 获取这个任务队列第一个任务synchronized (task.lock) {if (task.state == TimerTask.CANCELLED) {queue.removeMin();continue;}currentTime = System.currentTimeMillis();executionTime = task.nextExecutionTime;if (taskFired = (executionTime <= currentTime)) {if (task.period == 0) { // Non-repeating, removequeue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedulequeue.rescheduleMin(task.period < 0 ? currentTime - task.period : executionTime+ task.period);}}}//释放锁if (!taskFired)queue.wait(executionTime - currentTime);}if (taskFired) // Task fired; run it, holding no lockstask.run();} catch (InterruptedException e) {}}// while(true)}

newTasksMayBeScheduled变量用来表示是否需要继续等待新任务了.?

默认情况这个变量是true?, 并且这个变量一直是true的,只有两种情况的时候会变成?false?
? 1.当调用Timer的cancel方法
? 2.没有引用指向Timer对象了.

任务调度: mainLoop()方法中的一个while可以理解为一次任务调度:

STEP 1?:? 判断任务队列中是否还有任务, 如果任务队列为空了, 但是newTasksMayBeScheduled变量还是true, 表明需要继续等待新任务, 所以一直等待.

STEP 2?: 等待唤醒后, 再次判断队列中是否有任务. 如果还是没有任务,那么直接结束定时器工作了.

????????????? 因为queue只在两个地方被调用: addTask和cancel
?????????? ?1.向任务队列中增加任务会唤醒

????????????2.timer.cancel()的时候也会唤醒
?????那么这里如果还是empty,那么就是cancel的唤醒了,所以可以结束timer工作了.

STEP 3?: 从任务队列中取出第一个任务,即nextExecutionTime最小的那个任务.

STEP 4:?判断这个任务是否已经被取消. 如果已经被取消了,那么就直接从任务队列中移除这个任务(removeMin()?),然后直接进入下一个任务调度周期.

STEP 5?: 判断是否到了或者已经超过了这个任务应该执行的时间了.

??????如果到了?, 不会立即执行它,而是会在这次循环的最后来执行它.
??????这里做的事情可以看作是为下一个调度周期进行准备:包括:
?????? 1. 判断是否是重复(repeating)任务,如果 task.period == 0, 那么就不是重复任务,所以可以直接将这个任务从任务队列中移除了(removeMin()?),因为没有必要留到下一个调度周期中去了.
??????2. 如果是需要重复执行的任务, 那么就要重新设置这个任务的nextExecutionTime,即调用方法queue.rescheduleMin(long)?,这个方法中会调用fixDown(1)?负责重新调整任务队列的优先级顺序.

??????如果还没有到执行时间?,? 一直等到 queue.wait(executionTime - currentTime)

并且等待完毕后,似乎可以开始运行了, 但是这里设计成不立即运行,而是直接进入下一个任务调度周期.(因为taskFired =false,所以不会在这次进行执行的.)??????

STEP: 6?开始调用任务的run方法运行任务.

http://www.java1995.com/blog/item/516

?

读书人网 >编程

热点推荐