Timer 类位于java.util包下,其主要依托TimerTask,TaskQueue,TimerThread 来实现延时一次性任务 或者 间隔一段时间后重复执行。JDK1.5之后推荐使用 ScheduledThreadPoolExecutor,下一篇文章讲。
public class Timer {
//创建一个静态TaskQueue优先级队列(由小根堆实现),队列里存储的是TimerTask数组
private final TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
private final TimerThread thread = new TimerThread(queue);
public Timer(String name) {
thread.setName(name);
thread.start();
}
}
其大体如下图:
1、TimerTask
TimerTask是一个抽象类,implements Runnable接口,从而提供执行线程任务。主要是来约束任务状态,锁、任务执行时间等。
public abstract class TimerTask implements Runnable {
/**
* 内部维护一个对象锁,因为在TimerThread中一直循环判断是否要执行TimerTask,防止多线程问题
*/
final Object lock = new Object();
/**
* 任务状态,从下面的几个常量选择0,1,2,3.默认是0
*/
int state = VIRGIN;
/**
* 状态:还没有执行
*/
static final int VIRGIN = 0;
/**
* 计划执行此任务。如果是一次性(不重复)任务,还没有执行
*/
static final int SCHEDULED = 1;
/**
* 不重复的任务已经执行(或者正在执行),并且尚未取消
*/
static final int EXECUTED = 2;
/**
* 任务取消(调用了TimerTask.cancel方法)
*/
static final int CANCELLED = 3;
/**
* 下一次执行任务的时间。对于重复执行任务,任务执行后,会根据period字段进行更新(为了下次执行)
*/
long nextExecutionTime;
/**
* 重复任务的周期(以毫秒为单位)。正值表示固定速率执行。负值表示固定延迟执行。值0表示不重复
* 的任务。
*/
long period = 0;
protected TimerTask() {
}
/**
* run抽象方法
*/
public abstract void run();
/**
* 取消任务调用
*/
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
/**
* 返回此任务最近一次实际执行的计划执行时间。(如果在任务执行过程中调用此方法,则返回值为正在
* 执行的任务的计划执行时间。)
*/
public long scheduledExecutionTime() {
synchronized(lock) {
return (period < 0 ? nextExecutionTime + period
: nextExecutionTime - period);
}
}
}
其中关于TimerTask中period字段,当其字段值为0是为非重复性任务啊(执行一次),否则是重复性任务。正值表示固定速率执行。负值表示固定延迟(固定间隔)执行。对于nextExecutionTime 字段,假设一个任务的TimerTask字段中 nextExecutionTime 时间 小于等于当前时间,说明这个任务该执行了,判断是重复任务的话,更新字段 nextExecutionTime 。更新规则如下:
TimerTask task;
task.executionTime = task.period < 0 ? currentTime(当前时间戳,任务执行开始的时间) - task.period :
executionTime + task.period
解释一下固定速率和固定间隔区别:
固定速率(fixed-rate):匀速执行任务(无论这个任务是否完成,时间到了都执行,下次执行时间为 :currentTime(当前时间戳,任务执行开始的时间) - task.period )
固定间隔(fixed-delay):任务执行结束后,等待间隔时间后执行(但是在TimerThread类中run方法的fixed-delay 不是 执行结束时间 + period,而是直接拿的字段excutionTime值 + period,没搞懂)
2、TaskQueue
TaskQueue维护了一个TimerTask数组(使用优先级队列,小根堆),实现TimerTask根据字段 nextExecutionTime 来排序队列,提供啊add 、 getMin、get、removeMIn等方法。
其源代码:
class TaskQueue {
/**
* 维护TimerTask队列,从1开始
*/
private TimerTask[] queue = new TimerTask[128];
/**
* 队列中存在任务数量
*/
private int size = 0;
/**
* 返回队列中存在任务数量
*/
int size() {
return size;
}
/**
* 添加task到队列
*/
void add(TimerTask task) {
// 扩容
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2*queue.length);
queue[++size] = task;
fixUp(size);
}
/**
* 返回队列顶部,小根堆的顶部,下次执行时间最小的任务,也就是距离当前时间执行任务最近的
*/
TimerTask getMin() {
return queue[1];
}
TimerTask get(int i) {
return queue[i];
}
/**
* 移除头部,不解释fixDown和fixUp
*/
void removeMin() {
queue[1] = queue[size];
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);
}
/**
* 判断队列是否为空
*/
boolean isEmpty() {
return size==0;
}
/**
* 清空
*/
void clear() {
// Null out task references to prevent memory leak
for (int i=1; i<=size; i++)
queue[i] = null;
size = 0;
}
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size && j > 0) {
if (j < size &&
queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
j++; // j indexes smallest kid
if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
}
3、TimerThread
顾名思义线程,继承了Thread类,计时器的任务执行线程,该线程等待计时器队列中的任务,在它们到时执行它们,重新安排重复任务,并从队列中删除已取消的任务和已使用的非重复任务。需要TimerQueue提供任务队列。它需要单独一个线程来执行run方法,不断判断队列中的任务状态和执行时间。源代码如下:
class TimerThread extends Thread {
/**
* false时,说明已经没有引用,清空队列。
*/
boolean newTasksMayBeScheduled = true;
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
mainLoop();
} finally {
// 线程被杀死,或者队列任务执行完了
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
/**
* The main timer loop.
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
//任务是否该执行
boolean taskFired;
// 添加队列锁
synchronized(queue) {
// 队列是空的,并且没有清除引用,等待任务添加到队列
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // 还是空,清空
// 拿去队列中第一个任务,也就是最先执行的任务
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) { // 任务不是重复性的,直接移出队列
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // 重复性任务,设置任务下一次运行时间
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // 任务还没到时间执行,等到指定时间执行,wait锁会自动唤醒
queue.wait(executionTime - currentTime);
}
if (taskFired) // 任务可以执行
task.run();
} catch(InterruptedException e) {
}
}
}
}
总结
弊端:因为采用的是优先级队列,插入删除时间复杂度logn,频繁的出入队列性能需要考虑;单线程执行;没有异常处理;
ScheduledThreadPoolExecutor允许多线程。