作者:木扎尔特2502918527 | 来源:互联网 | 2023-10-11 08:48
问题引入: 假设当前时间为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 cancelledsynchronized(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-emptywhile (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 thinglong currentTime, executionTime;task &#61; queue.getMin();synchronized(task.lock) {if (task.state &#61;&#61; TimerTask.CANCELLED) {queue.removeMin();continue; // No action required, poll queue again}currentTime &#61; System.currentTimeMillis();executionTime &#61; task.nextExecutionTime;if (taskFired &#61; (executionTime<&#61;currentTime)) {if (task.period &#61;&#61; 0) { // Non-repeating, removequeue.removeMin();task.state &#61; TimerTask.EXECUTED;} else { // Repeating task, reschedulequeue.rescheduleMin(task.period<0 ? currentTime - task.period: executionTime &#43; task.period);}}}if (!taskFired) // Task hasn&#39;t yet fired; waitqueue.wait(executionTime - currentTime);}if (taskFired) // Task fired; run it, holding no lockstask.run();} catch(InterruptedException e) {}}
}
这样就很好理解了。
线程拿到queue对象锁&#xff0c;进入同步代码块后首当其冲会遇到while(queue.isEmpty() && newTasksMayBeScheduled)&#xff0c; 由于刚刚初始化Timer&#xff0c;queue自然是空的&#xff0c;并且newTasksMayBeScheduled的初始值也为true,所以执行了queue.wait() 因此当前持有锁的线程——Timer自然而然的进入了等待状态。
既然有wait()&#xff0c;自然就少不了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 >>&#61; 1;synchronized(queue) {if (!thread.newTasksMayBeScheduled)throw new IllegalStateException("Timer already cancelled.");synchronized(task.lock) {if (task.state !&#61; TimerTask.VIRGIN)throw new IllegalStateException("Task already scheduled or cancelled");task.nextExecutionTime &#61; time;task.period &#61; period;task.state &#61; TimerTask.SCHEDULED;}queue.add(task);if (queue.getMin() &#61;&#61; task)queue.notify();}
}
也即&#xff0c;当任务通过timer.schedule()加入至Timer时&#xff0c;会向队列中加入任务且唤醒Timer阻塞线程。
回过头来再看mainLoop()方法。本题的关键就在于while(queue.isEmpty()&newTasksMayBeScheduled)&#xff0c;下方的注释已经写得非常明白了。
private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// 等待新的任务加入队列 // 注意: 如果所有的任务已经执行完毕&#xff0c;由于下方代码会主动调用run()&#xff0c;因此同样会再次执行此处代码// 此时&#xff0c;任务队列中已经没有任务了&#xff0c;Timer线程又一次回到了漫长的等待中&#xff0c;等待新的任务降临while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait();if (queue.isEmpty())break; // 如果队列为空&#xff0c;即没有任务可执行任务&#xff0c;且将来也不会有任务(没有等待的必要了)&#xff0c;因此断掉循环&#xff0c;最终结束run()&#xff0c;线程终止// Queue nonempty; look at first evt and do the right thinglong currentTime, executionTime;task &#61; queue.getMin(); //若队列不为空&#xff0c;则获取队列头部的任务并加以执行(众所周知&#xff0c;队列的FIFO&#xff0c;头部存储的是最早被加入的任务)synchronized(task.lock) { //队列的对象锁 注意这里的lock不是ReentrantLock之流&#xff0c;不过是一个普通的Object对象而已。if (task.state &#61;&#61; TimerTask.CANCELLED) {queue.removeMin();continue; // No action required, poll queue again}currentTime &#61; System.currentTimeMillis(); // 当前时间executionTime &#61; task.nextExecutionTime; // 任务计划执行时间// 若任务计划执行时间早于或等于当前时间&#xff0c;那么就需要执行任务了&#xff0c;所以进入if()内部执行任务&#xff0c;既然任务开始执行了&#xff0c;所以taskFired被赋值true,表示任务已过期。// 若任务计划执行时间晚于当前时间&#xff0c;说明还没有轮到该任务执行呢&#xff0c;因此不执行if(),taskFired被赋值为false&#xff0c;表示任务尚未过期。if (taskFired &#61; (executionTime<&#61;currentTime)) { if (task.period &#61;&#61; 0) { // 0表示任务不重复执行queue.removeMin(); //移除头部任务task.state &#61; TimerTask.EXECUTED; //将当前任务的状态标记为"已执行" 注意: 这里利用了异步执行的思想&#xff0c;任务本身由于继承了Runnable接口&#xff0c;被作为一个线程异步执行&#xff0c;与此同时Timer线程也不耽搁&#xff0c;为该任务的描述性属性赋值。} else { // 重复执行任务queue.rescheduleMin(task.period<0 ? currentTime - task.period: executionTime &#43; task.period);}}}if (!taskFired) // 任务尚未开始&#xff0c;还需要等待&#xff0c;等待的时长 &#61; 任务即将执行的时间 - 当前时间queue.wait(executionTime - currentTime);}if (taskFired) // 任务已过期(执行过了)&#xff0c;再次调用自身线程的run()task.run();} catch(InterruptedException e) {}}
}