热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在java.lang.Timer为非守护线程的情况下,加入到TimerTask的任务执行完毕了,Timer线程仍在继续运行的原因

问题引入:假设当前时间为2019-03-1322:10:00,定义任务A在3分钟后执行,任务B在5分钟后执行,我们将A和B加入至Timer

问题引入:  假设当前时间为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) {}}
}

 


推荐阅读
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 在Java项目中,当两个文件进行互相调用时出现了函数错误。具体问题出现在 `MainFrame.java` 文件中,该文件位于 `cn.javass.bookmgr` 包下,并且导入了 `java.awt.BorderLayout` 和 `java.awt.Event` 等相关类。为了确保项目的正常运行,请求提供专业的解决方案,以解决函数调用中的错误。建议从类路径、依赖关系和方法签名等方面入手,进行全面排查和调试。 ... [详细]
  • 开发日志:201521044091 《Java编程基础》第11周学习心得与总结
    开发日志:201521044091 《Java编程基础》第11周学习心得与总结 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • 寒假作业解析:第三周 2月12日 第7题
    尽快完成之前的练习任务!每日一练2.1 Problem A Laurenty and Shop 的题目要求是选择两条不同的路线以最小化总的等待时间。简要分析:通过对比不同路线的等待时间,可以找到最优解。此问题可以通过动态规划或贪心算法来解决,具体取决于路线的复杂性和约束条件。 ... [详细]
  • 设计实战 | 10个Kotlin项目深度解析:首页模块开发详解
    设计实战 | 10个Kotlin项目深度解析:首页模块开发详解 ... [详细]
  • 洛谷 P4035 [JSOI2008] 球形空间生成器(高斯消元法 / 模拟退火算法)
    本文介绍了洛谷 P4035 [JSOI2008] 球形空间生成器问题的解决方案,主要使用了高斯消元法和模拟退火算法。通过这两种方法,可以高效地求解多维空间中的球心位置。文章提供了详细的算法模板和实现代码,适用于 ACM 竞赛和其他相关应用场景。数据范围限制在 10 以内,确保了算法的高效性和准确性。 ... [详细]
  • 本文介绍了如何利用ObjectMapper实现JSON与JavaBean之间的高效转换。ObjectMapper是Jackson库的核心组件,能够便捷地将Java对象序列化为JSON格式,并支持从JSON、XML以及文件等多种数据源反序列化为Java对象。此外,还探讨了在实际应用中如何优化转换性能,以提升系统整体效率。 ... [详细]
  • 在洛谷 P1344 的坏牛奶追踪问题中,第一问要求计算最小割,而第二问则需要找到割边数量最少的最小割。通过为每条边附加一个单位权值,可以在求解最小割时优先选择边数较少的方案,从而同时解决两个问题。这种策略不仅简化了问题的求解过程,还确保了结果的最优性。 ... [详细]
  • 蓝桥杯算法实战:节点选取策略优化分析
    本文针对蓝桥杯算法竞赛中的节点选取策略进行了深入分析与优化。通过对比不同节点选择方法的效果,提出了基于贪心算法和动态规划的综合优化方案,旨在提高算法效率和准确性。实验结果表明,该优化策略在处理大规模数据集时表现出色,显著提升了算法性能。 ... [详细]
  • 在Python多进程编程中,`multiprocessing`模块是不可或缺的工具。本文详细探讨了该模块在多进程管理中的核心原理,并通过实际代码示例进行了深入分析。文章不仅总结了常见的多进程编程技巧,还提供了解决常见问题的实用方法,帮助读者更好地理解和应用多进程编程技术。 ... [详细]
  • 开发心得:成为SGU475智能筏工的策略与技巧 ... [详细]
  • 如何使用 com.jme3.input.FlyByCamera 构造函数及其代码示例详解 ... [详细]
  • 题目链接:POJ 2777。问题描述:给定一个区域,需要进行多次涂色操作,并在每次操作后查询某个区间内的不同颜色数量。解决方案:由于题目中颜色种类不超过30种,可以利用线段树的懒惰更新策略来高效处理这些操作。通过懒惰标记,避免了不必要的节点更新,从而显著提高了算法的效率。此外,该方法还能有效应对大规模数据输入,确保在合理的时间内完成所有操作。 ... [详细]
author-avatar
木扎尔特2502918527
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有