Timer 线程调度任务
本质上每个Timer对象都是一个单个后台线程Thread,用于依次执行该对象的所有任务。当Timer对象被new出来时,后台线程就会启动,没有任务会wait(),直到添加任务后被唤醒。
添加的任务应该是能很快完成的。如果某个任务执行时间过长(超过间隔时间period),因为Timer是单线程,它会导致后面任务的执行时间被延迟执行和快速执行。
假如我们创建了一个定时器对象Timer,设置执行间隔period=10s。
我们的理想状态是,每个任务完成的时间小于10s,这样每个任务执行的时间轴是0,10,20,30...
但有可能每个任务执行的时间有波动,导致第一次执行时间大于10s且结束后会立即执行第二次任务,也会影响后续的任务执行时间,达不到一个定时的效果(任务只会延后,不会提前执行)。
构造器
public Timer(String name, boolean isDaemon) {
// 定时器名称(线程名)
thread.setName(name);
// 是否设置定时器为守护线程,默认是false
thread.setDaemon(isDaemon);
// 对象创建好立即执行
thread.start();
}
说一下isDemon作用,如果我们在main线程中创建定时器(创建好是立即在后台执行的),这时候即使main线程结束了,定时器仍会在执行。如果setDemon(true),则定时器是一个后台执行的守护线程,main线程如果结束了,定时器也会立即结束。
成员参数
// 定时器任务队列,与TimerThread共享
private final TaskQueue queue = new TaskQueue();
// Timer后台执行线程
private final TimerThread thread = new TimerThread(queue);
方法
// 一次性任务
public void schedule(TimerTask task, Date time);
// period传递给sched方法为负数
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, Date firstTime, long period);
// period传递给sched方法为正数
public void scheduleAtFixedRate(TimerTask task, long delay, long period);
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);
// 终止定时器
public void cancel();
// 清理queue中cancelled状态的任务,返回删除的数量
public int purge()
TimerTask
能被Timer调度一次或多次的任务抽象类,实现了Runnable接口;另外同一个TimerTask能被不同Timer调度
成员参数:
// 一个TimerTask对象能被多个Timer调度,修改该对象成员变量时需要使用synchronized进行同步
final Object lock = new Object();
/**
实例对象自己维护了一个状态state:
VIRGIN :刚创建的TimerTask默认值(初始值)
SCHEDULED :调用schedule方法放入queue前,设置peiod、nextExecutionTime、state
EXECUTED :非重复任务已执行或正在执行中,还未被取消
CANCELLED :任务已被取消(调用cancel)
*/
int state = VIRGIN;
// 下次执行时间,格式System.currentTimeMillis
// 对于重复任务,该字段在每次任务执行之前更新
long nextExecutionTime;
// 重复任务的执行间隔时间 毫秒ms
// 如果是正数,表示固定速度执行(每次执行时间在创建时就确定了,时间轴不变)
// 如果是负数,表示固定延迟执行(相对上次执行时间固定间隔,时间轴可能会变大)
long period = 0;
方法
// 实现定时任务的业务逻辑
public abstract void run();
// 取消任务,返回当前state==SCHEDULED,并将state设置为CANCELLED
public boolean cancel();
TaskQueue
Timer的内部优先队列,是一个二叉堆,按nextExecutionTime排序的最小堆。
这边讲个小知识,优先队列虽然本质是个完全二叉树,但实现是通过数组。每次取queue[1],有人可能会问为什么不是取queue[0]呢?因为一棵深度为n的完全二叉树节点总个数是2n-1,数组长度一般都是2n,所以数组多出一个位置queue[0]不存放数据。这样还有个好处,如果一个二叉树节点的数组位置是queue[i],那么它两个左右子节点的可以分别表示成queue[2i],queue[2i+1]。
TimerThread
Timer内部线程对象,继承于Thread
成员变量
//
boolean newTasksMayBeScheduled = true;
//
private TaskQueue queue;
方法
// 不废话,直接干(TimerThread的run方法体)
private void mainLoop() {
// Timer后台线程一直执行
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 任务队列为空 线程进行等待
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
// 等待结束后,队列仍为空则终止定时器
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
// 取出队列中优先级最高的任务,但不从队列中移除
task = queue.getMin();
synchronized(task.lock) {
// 判断任务是否被取消
if (task.state == TimerTask.CANCELLED) {
queue.removeMin(); // 移除该任务
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
// 判断任务是否到达执行的时间
if (taskFired = (executionTime<=currentTime)) {
// 一次性任务,执行完丢弃
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // 重复性任务,需要
queue.rescheduleMin(
// 设置该任务下次的执行时间nextExecutionTime
// period<0 固定延迟:当前时间+period
// period>0 固定速率:预期执行时间+period
task.period<0 ? currentTime - task.period : executionTime + task.period);
}
}
}
if (!taskFired) // 任务还未到执行时间,继续等待固定时长
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run(); // 执行任务
} catch(InterruptedException e) {
}
}
}