Timer 定时器相信都不会陌生,之所以拿它来做源码分析,是发现整个控制流程可以体现很多有意思的东西。
在业务开发中经常会遇到执行一些简单定时任务的需求,通常为了避免做一些看起来复杂的控制逻辑,一般考虑使用 Timer 来实现定时任务的执行,下面先给出一个最简单用法的例子:
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
// scheduledExecutionTime() 返回此任务最近开始执行的时间
Date date = new Date(this.scheduledExecutionTime());
System.out.println("timeTask run " + date);
}
};
// 从现在开始每间隔 1000 ms 计划执行一个任务
timer.schedule(timerTask, 0, 1000);
Timer 概述
Timer 可以按计划执行重复的任务或者定时执行指定任务,这是因为 Timer 内部利用了一个后台线程 TimerThread 有计划地执行指定任务。
Timer:是一个实用工具类,该类用来调度一个线程(schedule a thread),使它可以在将来某一时刻执行。 Java 的 Timer 类可以调度一个任务运行一次或定期循环运行。 Timer tasks should complete quickly. 即定时器中的操作要尽可能花费短的时间。
TimerTask:一个抽象类,它实现了 Runnable 接口。我们需要扩展该类以便创建自己的 TimerTask ,这个 TimerTask 可以被 Timer 调度。
一个 Timer 对象对应的是单个后台线程,其内部维护了一个 TaskQueue,用于顺序执行计时器任务 TimeTask 。
Timer
Timer 中优先队列的实现
TaskQueue 队列,内部用一个 TimerTask[] 数组实现优先队列(二叉堆),默认最大任务数是 128 ,当添加定时任务超过当前最大容量时会这个数组会拓展到原来 2 倍。
TaskQueue
优先队列主要目的是为了找出、返回并删除优先队列中最小的元素,这里优先队列是通过数组实现了平衡二叉堆,TimeQueue 实现的二叉堆用数组表示时,具有最小 nextExecutionTime 的 TimerTask 在队列中为 queue[1] ,所以堆中根节点在数组中的位置是 queue[1] ,那么第 n 个位置 queue[n] 的子节点分别在 queue[2n] 和 queue[2n+1] 。关于优先队列的数据结构实现,这里推荐一篇文章:数据结构与算法学习笔记 - 优先队列、二叉堆、左式堆。
按照 TaskQueue 的描述:This class represents a timer task queue: a priority queue of TimerTasks, ordered on nextExecutionTime.这是一个优先队列,队列的优先级按照 nextExecutionTime 进行调度。
也就说 TaskQueue 按照 TimerTask 的 nextExecutionTime 属性界定优先级,优先级高的任务先出队列,也就先执行任务调度。
队列操作
如上图所示,列举了优先队列中部分操作的实现,优先队列插入和删除元素的复杂度都是O(logn),所以add, removeMin 和 rescheduleMin方法的性能都是不错的。从上图可以知道,获取下一个计划执行任务时,取队列的头出列即可,为了减少额外性能消耗,移除队列头部元素的操作是先把队尾元素赋值到队首后,再把队尾置空,队列数量完成减一后进行优先权值操作。再下面看看保证优先队列最核心的两个方法fixUp和fixDown。
两个方法的核心思路都是通过向上或向下调整二叉堆中元素所在位置,保持堆的有序性:
fixUp 是将元素值小于父节点的子节点与父节点交换位置,保持堆有序。交换位置后,原来的子节点可能仍然比更上层的父节点小,
所以整个过程需要循环进行。这样一来,原来的子节点有可能升级为层级更高的父节点,类似于一个轻的物体从湖底往上浮直到达到其重力与浮力相平衡的过程。