【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
Timer只是管理着一个TimerTask任务队列(queue)而TimerTask的真正执行是由TimerThread来执行的,TimerThread继承了Thread,它的主要工作就是不断从优先级队列queue中取出任务检查是否到了可以执行的时间,如果可以执行就调用TimerTask的run方法。
非常值得注意的是任务队列(queue)中的TimerTask虽然实现了Runnable接口,但是在TimerThread中并没有启动新的线程来执行它,只有Thread的start方法才能启动新的线程。所以如果TimerTask中的run方法是死循环,那么可能Timer的任务队列中的TimerTask永远得不到执行的机会。还是先看一下TimerThread的代码吧。
import java.util.TimerTask;
public class TimerThread extends Thread {
boolean newTasksMayBeScheduled = true;
/**
* 任务队列的引用
*/
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
mainLoop();
} finally {
// 任务结束,例如Timer 执行了cancel
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); //清除废弃对象的引用
}
}
}
/**
*
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 如果暂时没有任务,并且TimerThread线程没有被取消就等待
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // 如果任务队列为空并且TimerThread已经被取消了,直接结束
long currentTime, executionTime;
//我们已经知道queue队列是一个按下一次执行时间排序的优先队列
//所以,这里是获取到执行时间最近的TimerTask
task = queue.getMin();
//主要防止TimerTask的state域被其他线程修改,而造成数据不一致
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue;
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;//下一次执行的时间
//如果下一次执行时间小于等于当前时间说明可以执行了
if (taskFired = (executionTime<=currentTime)) {
// period等于0说明不是周期执行的,就直接移除并把TimerTask的状态修改为执行过了
if (task.period == 0) {
queue.removeMin();
task.state = TimerTask.EXECUTED;
}
// 这里比较巧妙,重新计算下一次的执行时间,我们知道period<0是按固定的时间间隔执行
//看下面的代码当period<0的时候下一次的执行时间(注意减的是负数)
//nextExecutionTime = currentTime - period,所以每一次计划执行完之后的period再执行
//period>0是按固定的频率执行,显然在executionTime<=currentTime条件下同
//executionTime + task.period计算一下次执行时间就尽量保证了在每两次计划执行期间的period
//尽量相等,因为executionTime每一次都是计算出来的是固定的,所以它能保证频率固定
else {
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
// 任务还没有被移除,说明最小的执行时间都还没有到,直接等待
if (!taskFired)
queue.wait(executionTime - currentTime);
}
// 如果任务(TimerTask)已经从队列中移除,说明已经到了执行时间,可以执行
if (taskFired)
task.run();//只是调用的run方法没有启用新的线程
} catch(InterruptedException e) {
}
}
}
}
在mainLoop中有这样一句代码:
queue.rescheduleMin(task.period<0 ? currentTime - task.period
: executionTime + task.period);
TimerTask中以一个nextExecutionTime字段是表示下一次的执行时间的,上面的语句就是重新设置队列优先级最高(堆顶)元素的下一次执行时间的语句。当period<0的时候currentTime-period就是加上period绝对值。currentTime是当前的时间是变化的,period是固定的,所以固定延迟(fixed-delay)就好理解了,就是不管TimerTask执行了多久,重新安排执行计划的时候倒是按当前时间向后延迟period这么长的时间。
当period>0的时候表示固定频率(fixed-rate)执行,executionTime就是TimerTask的 nextExecutionTime , 当period>0的时候 nextExecutionTime 每一次都是有上一次的 nextExecutionTime +period计算来的,它两次执行区间可能就不需要等period这么长时间了。例如:没有延迟立即开始执行,每一次执行2分钟,period为3。使用固定延迟就是0分钟执行第一次,执行了2分钟然后下一次执行时间就是2+3=5,然后在执行2分钟第三次执行时间就是7+3=10分钟的时候。而使用固定频率的方式就是第一次执行是在0分钟,下一次执行时间是在0+3=3分钟的时候执行,然后他就会在第3分钟执行,第二次是在3+3=6分钟的时候执行,它和执行的时间基本没有关系,就是一个等差数列。
因为Timer主要对外提供的接口就是schedule方法,所以Timer本身没有太多的内容,但是它有一个非常好技巧,就是他对外提供的不同参数的固定频率执行,固定延迟执行很多接口,但是它的内部实现只是巧妙的通过参数的变换把它抽象都了一个方法中。有时候觉得别人的类写的特别好用从这里就可以看出一点,不要让使用接口的人来控制变化来做不同的实现,很少有人愿意记住,而是通过直观的方法名来对外提供接口,我们内部实现来控制变化。看Timer提供的的各种schedule接口基本都是通过period等参数变化实现的。而我们看到的就是直观的schedule方法。
来源:oschina
链接:https://my.oschina.net/u/2474629/blog/691488