问题引入: 假设当前时间为2019-03-13 22:10:00 ,定义任务A在3分钟后执行,任务B在5分钟后执行,我们将A和B加入至Timer中,根据Timer中TaskQueue顺序启动任务的原则,8分钟后任务A和任务B都会执行完毕。问题来了,明明任务全部执行完毕了,为什么Timer线程仍在继续 执行,就仿佛停不下来了呢?
首先,我们来看看Timer的构造函数
public Timer() {
this("Timer-" + serialNumber());
}
接着,点开this ()发现实际调用的构造函数如下
public Timer(String name) {
thread.setName(name);
thread.start();
}
/**
* The timer thread.
*/
private final TimerThread thread = new TimerThread(queue);
说白了就是Timer自己启动了一个私有线程(外部无法调用)去执行相关操作。因此,需要看看TimeThread的run()到底是怎么写的
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
再看mainLoop()是如何实现的
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
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)) {
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) {
}
}
}
这样就很好理解了。
线程拿到queue对象锁,进入同步代码块后首当其冲会遇到while(queue.isEmpty() && newTasksMayBeScheduled), 由于刚刚初始化Timer,queue自然是空的,并且newTasksMayBeScheduled的初始值也为true,所以执行了queue.wait() 因此当前持有锁的线程——Timer自然而然的进入了等待状态。
既然有wait(),自然就少不了notify()。让我们看看Timer.java中的哪个部分调用了notify()
//外部类 将task1加入至Timer中
timer.schedule(task1, Date date);
public void schedule(TimerTask task, Date time) {
sched(task, time.getTime(), 0);
}
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);
if (queue.getMin() == task)
queue.notify();
}
}
也即,当任务通过timer.schedule()加入至Timer时,会向队列中加入任务且唤醒Timer阻塞线程。
回过头来再看mainLoop()方法。本题的关键就在于while(queue.isEmpty()&newTasksMayBeScheduled),下方的注释已经写得非常明白了。
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 等待新的任务加入队列
// 注意: 如果所有的任务已经执行完毕,由于下方代码会主动调用run(),因此同样会再次执行此处代码
// 此时,任务队列中已经没有任务了,Timer线程又一次回到了漫长的等待中,等待新的任务降临
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // 如果队列为空,即没有任务可执行任务,且将来也不会有任务(没有等待的必要了),因此断掉循环,最终结束run(),线程终止
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
task = queue.getMin(); //若队列不为空,则获取队列头部的任务并加以执行(众所周知,队列的FIFO,头部存储的是最早被加入的任务)
synchronized(task.lock) { //队列的对象锁 注意这里的lock不是ReentrantLock之流,不过是一个普通的Object对象而已。
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis(); // 当前时间
executionTime = task.nextExecutionTime; // 任务计划执行时间
// 若任务计划执行时间早于或等于当前时间,那么就需要执行任务了,所以进入if()内部执行任务,既然任务开始执行了,所以taskFired被赋值true,表示任务已过期。
// 若任务计划执行时间晚于当前时间,说明还没有轮到该任务执行呢,因此不执行if(),taskFired被赋值为false,表示任务尚未过期。
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) { // 0表示任务不重复执行
queue.removeMin(); //移除头部任务
task.state = TimerTask.EXECUTED; //将当前任务的状态标记为"已执行" 注意: 这里利用了异步执行的思想,任务本身由于继承了Runnable接口,被作为一个线程异步执行,与此同时Timer线程也不耽搁,为该任务的描述性属性赋值。
} else { // 重复执行任务
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // 任务尚未开始,还需要等待,等待的时长 = 任务即将执行的时间 - 当前时间
queue.wait(executionTime - currentTime);
}
if (taskFired) // 任务已过期(执行过了),再次调用自身线程的run()
task.run();
} catch(InterruptedException e) {
}
}
}