美文网首页Java 杂谈
java.util.Timer全面解析

java.util.Timer全面解析

作者: 日光倾城_700b | 来源:发表于2018-05-13 18:41 被阅读8次

java.util.Timer:线程的工具,用于在后台线程中安排将来执行的任务。可以安排一次性执行任务,或定期重复执行任务。任务应该是能快速完成的。这个类是线程安全的:多线程可以共享一个Timer对象,而不需要外部同步。

这个类可以扩展到大量同时计划的任务(数千个都没有问题)。在内部,它使用二进制堆来表示其任务队列,因此调度任务的成本为O(log n),其中n是并发计划任务的数量。

下面基于JDK1.8源码全面分析Timer的实现。

来看一下Timer的属性:

TaskQueue queue = new TaskQueue();//任务队列

TimerThread thread = new TimerThread(queue);//持有任务队列的Thread。

先简单看一下TaskQueue和TimerThread的定义。

TaskQueue:表示一个计时器任务队列,一个TimerTasks的优先级队列,通过nextExecutionTime字段排序,内部使用堆实现。

属性:

TimerTask[]queue = new TimerTask[128];//采用堆排序的数组

int size = 0;代表优先队列中任务数;

TimerThread:继承自Thread。相当于一个“助手类”,实现了定时器的任务执行线程,该线程功能包括等待定时器队列上的任务,在它们触发时执行它们,重新安排重复任务,并删除取消的任务,并从队列中去除非重复任务。

属性:

下面看一下Timer的构造函数:

构造一个Timer的对象,会触发thread,那么我们看一下TimerThread干了些什么。

主要逻辑:

1、在queue上加同步器锁,当queue为空且还有线程持有timer对象,Thread wait。直到得到notify通知,这个notify是当有元素进入queue或者newTasksMayBeScheduled被置为false时会触发。

2、如果queue为空,说明没有需要处理的任务,跳出循环。

3、从queue取最近将要触发的任务,如果这个任务是CANCELLED状态,把它从queue中移除,并continue;否则,如果任务没有达到触发时间,wait(触发间隔时间)。当任务达到触发时间,如果这个任务不是重复跑的,把它从queue中移除,并把这个任务的状态置为EXECUTED。如果它是需要重复执行的,会设置它的下一次触发时间。然后执行任务的run方法执行实际的逻辑。执行完了之后,因为是一个while的无限循环,又会开始上述逻辑判断。

在上述第3条中,其实我们是依赖queue维护了一个最近触发时间的小根堆,我们看下queue的几个核心方法:

任务加入队列:

先做容量判断:数组将满时扩大为原来两倍;否则任务加入队列,注意第一个任务是放在queue[1]的位置;然后做fixUp(size);

这个方法实际是在构建一个小根堆,传入的k即为当前队列任务数量。且任务从数组下标1位置开始存放。当队列容量大于1时,先取size/2对应的元素与size对应的元素做比较,如果queue[size/2]的下一次触发时间>queue[size]的下一次触发时间,则把queue[size/2]与queue[size]对应的任务做交换。循环直到数组下标为1的元素。这样就把最近触发的任务放到了数组1的位置,最后触发的任务放到了数组末尾。

获取当前队列最近将要执行的任务

移除当前队列第一个任务

将队列最后一个任务赋给queue[1],最后一个位置设为null,任务总数减一;关键我们看一下fixDown(1)做了什么。

因为队首任务被置为了队尾的任务,则它肯定破坏了最小堆,这个方法就是要重新建立小根堆:

1、取2k对应的节点,因为k的孩子节点是2k和2k+1,

2、先判断2k和2k+1对应的值,取他们中小的,设置j的值

3、比较queue[k]与queue[j],如果执行时间不是小于关系,交换这两个任务,让下次执行时间越大的元素下移

4、循环直到队列最后位置,这样的话,执行时间离现在最久的元素被放到了队列末尾;

重复执行的任务重新设置其下次执行时间:

更新当前队首的任务的下次执行执行,同时重新调整小根堆。

目前我们还只是看了Timer的构造函数,下面我们来看Timer设置任务调度的几个方法:

schedule(TimerTasktask, long delay);

schedule(TimerTasktask, Date time);

schedule(TimerTasktask, long delay, long period);

schedule(TimerTask task, Date firstTime, long period);

以上四个方法分别表示触发一次和按频率触发多次;

scheduleAtFixedRate(TimerTasktask, long delay, long period);

scheduleAtFixedRate(TimerTasktask, Date firstTime, long period)

按固定的频率触发多次;

那么这两类方法都有按频率重复执行的逻辑,他们有什么差别呢?

我们看一下代码:

一个传的是正数一个传的是负数。而在TimerThread的mainLoop方法中,

可以看到如果period<0,下次调度时间是当前时间+间隔时间(减一个负数就是加一个正数),否则下次调度时间是下次执行时间+间隔时间,就是说他们下次触发的时间维度不一样。

以上两类方法最终都会调用

设置task的属性,将其加入到queue中,并且如果这个task是队列的第一个任务,notify阻塞在queue上面的thread。整个入队的过程持有queue的监视器锁。

下面我们看一下TimerTask,它实现了Runnable接口,其run方法由子类去实现,它有状态字段state表示任务的执行状态,包括:

默认状态是VIRGIN。

取消一个任务则调用其cancel方法

只是简单把任务状态置为CANCELLED。

总结:

Timer通过TimerThread循环触发将要执行的任务,通过TaskQueue维护一个待执行任务列表,TaskQueue内部通过一个小根堆实现待执行任务的排序。拨开底层,我们对Timer这个调度器能有一个更深的理解。

相关文章

  • java.util.Timer全面解析

    java.util.Timer:线程的工具,用于在后台线程中安排将来执行的任务。可以安排一次性执行任务,或定期重复...

  • ambari

    Ambari架构源码解析 ambari使用最全面解析

  • 2018-08-30

    全面解析java注解 ·

  • IOS CGD全面解析

    IOS CGD全面解析

  • 全面深度解析iOS端URL编码和解码过程

    全面深度解析iOS端URL编码和解码过程全面深度解析iOS端URL编码和解码过程

  • iOS开发之UITableView全面解析

    iOS开发之UITableView全面解析

  • this全面解析

    this到底是什么?this是在运行的进行绑定的,并不是编写时绑定的。他的上下文取决于函数调用的各种环境。this...

  • this全面解析

    看了《你不知道的js》中this这一章,觉得有必要自己记录总结一下,不然过段时间,可能又忘了。 每个函数的this...

  • this全面解析

    1 定义 谁调用的函数,该函数的this就指向谁。 2 四种绑定规则 2.1 默认绑定,this指向全局对象,严格...

  • 全面解析this

    每个函数的this实在调用时被绑定的,完全取决于函数的调用位置。我们找到函数的调用位置,然后运用以下四种绑定规则来...

网友评论

    本文标题:java.util.Timer全面解析

    本文链接:https://www.haomeiwen.com/subject/djphrftx.html