热门标签 | 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) {}}
}

 


推荐阅读
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • 本文整理了Java中java.lang.NoSuchMethodError.getMessage()方法的一些代码示例,展示了NoSuchMethodErr ... [详细]
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社区 版权所有