API
Timer类用于延迟任务的执行。
schedule(TimerTask task, **long **delay)
,指定延迟时间schedule(TimerTask task, Date time)
,指定时间schedule(TimerTask task, **long **delay, **long **period)
,指定延迟时间和重复周期schedule(TimerTask task, Date firstTime, **long **period)
,指定初始时间和重复周期
源码解读
使用API好奇的地方主要有两点:
- 如何定时执行——如何保证准确的定时执行?这件事看起来几乎无法完成
- 多个任务,当前一个任务没执行完,后续任务如何定期执行。
遗憾的是,源码解读之后,发现Timer没有给出很好的答案。它大致上会把任务放入优先级队列中,执行时间为key,轮询取出时间最早的进行执行。如果前边任务执行时间过长,后续任务只会往后延迟。所以它的解法非常简单,既不保证精确的时间,也不保证任务一定按指定时间进行。
整体上,它会启动一个线程子类对象,该线程会执行轮询操作。
// Timer
public Timer(String name) {
thread.setName(name);
thread.start();
}
// TimerThread
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
Timer底层的队列是数组实现的最小堆,每次添加任务到队列中,会设置周期period,调度时间nextExecutionTime。为了确保线程安全性,它会对queue和task本身分别加锁。
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// Constrain value of period sufficiently to prevent numeric
// overflow while still being effectively infinitely large.
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
synchronized(task.lock) {
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
// 设置时间
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
queue.add(task);
// 如果当前添加的任务为最早执行的任务,那么尝试唤醒轮询线程
// 因为在轮询过程中,可能由于队列为空,等待在queue上
if (queue.getMin() == task)
queue.notify();
}
}
轮询线程的核心方法:
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
// 加锁轮询,如果queue为空,则等待
synchronized(queue) {
// Wait for queue to become non-empty
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)) {
// 如果不需要周期执行,则remove,否则重新设定下一次时间
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
// 执行
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
缺点
解读源码后,可以理解它的一些缺点:
1、首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。
2、其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为
3、Timer在执行定时任务时只会创建一个线程任务,如果存在多个线程,若其中某个线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,会导致下一个任务执行时间滞后
————————————————
来自《Java并发编程实践》