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

Timer源码分析

java.util.Timer简介Timer是用于管理在后台执行的延迟任务或周期性任务,其中的任务使用java.util.TimerTask表示。任务的执行方式有两种

java.util.Timer简介

        Timer是用于管理在后台执行的延迟任务或周期性任务,其中的任务使用java.util.TimerTask表示。任务的执行方式有两种:

按固定速率执行:即scheduleAtFixedRate的两个重载方法
按固定延迟执行:即schedule的4个重载方法

        我们通过源码来分析它的特性

        首先,看一个例子:

public static void main(String[] args) throws Exception {Timer timer = new Timer();Thread.sleep(10000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("haha");try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}, 1000, 1000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("heihei");}}, 1000,1000);}

这个例子中,我们在主线程中实例化了一个Timer对象,然后在该对象中添加了两个任务。

那么这些任务是怎么执行的?有什么样的一些特性?我们通过源码一步步分析,来梳理该组件的一些特点。

我们从Timer初始化开始分析:

Timer内部有两个属性

private final TaskQueue queue = new TaskQueue();private final TimerThread thread = new TimerThread(queue);

有一个属性queue,是用来存储任务的,我们上面的例子中,添加了两个任务,其实就是new 了两个TimeTask对象,放在了这个queue中。

        另一个属性是thread,在初始化的时候就会启动该线程:

public Timer(String name) {thread.setName(name);thread.start();}

 也就是说在在实例化Timer的同时,我们系统中就生成了一个新的线程,接下来看下该线程的run方法。该TimerThread的run方法中就是一个mianLoop方法:

public void run() {try {mainLoop();} finally {// Someone killed this Thread, behave as if Timer cancelledsynchronized(queue) {newTasksMayBeScheduled = false;queue.clear(); // Eliminate obsolete references}}}

下面逐行解释一下mainLoop的代码含义 

private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// Wait for queue to become non-empty//如果queue是空的,会一直等待下去,直到Timer添加任务时notify后被唤醒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 thinglong currentTime, executionTime;task = queue.getMin();synchronized(task.lock) {if (task.state == TimerTask.CANCELLED) {//如果这个任务被取消了,就从queue中移除queue.removeMin();continue; // No action required, poll queue again}currentTime = System.currentTimeMillis();executiOnTime= task.nextExecutionTime;//如果下次执行的时间早于当前时间,则说明达到了执行的时间点,即taskFired为trueif (taskFired = (executionTime<=currentTime)) {if (task.period == 0) { // Non-repeating, remove//如果period是0,代表该任务不是一个重复执行的任务,可以从queue中移除queue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedule//如果是重复任务,则修改下次执行的时间:如果period为正数,代表以固定的频率执行任务,如果是负数,代表以固定的延迟时间执行任务queue.rescheduleMin(task.period<0 ? currentTime - task.period: executionTime + 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) {}}}

   这个方法整个就是一个大的死循环。
        总结这些对象及属性引用关系如下,Timer对象中有queue,添加任务的时候其实就是在queue中添加一个TimeTask对象,而Timer对象和TimerThread引用的是同一个queue,而ThimeThread的mainLoop方法中的逻辑的不断循环判断queue中各个TimeTask对象的状态,进而进行相应的逻辑处理



上面是实例化Timer的源码分析,总结就是:在实例化的同时,产生了一个新的线程,这个线程在一个大循环中,在不断的轮询queue,如果这个queue是空的,改线程就会调wait方法等待。

        下面是添加任务的方法,在添加任务的时候,会判断queue之前是不是为空,如果为空,就说明执行任务的线程在wait,需要进行唤醒。

public void schedule(TimerTask task, long delay, long period) {if (delay <0)throw new IllegalArgumentException("Negative delay.");if (period <= 0)throw new IllegalArgumentException("Non-positive period.");sched(task, System.currentTimeMillis()+delay, -period);}

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);
//这里结合TimerThread的mainLoop方法看,如果当前加入的task是queue中最小的那个,说明之前的queue是空的,TimerThread在wait,需要被唤醒if (queue.getMin() == task)queue.notify();}}

void rescheduleMin(long newTime) {queue[1].nextExecutiOnTime= newTime;fixDown(1);}

重复任务需要重新计算下次执行的时间,然后通过fixDown对任务进行重排,将其顺序与后面的任务进行对比下。这种方式并非排序,而是找到一个合适的位置来交换,因为并不是通过队列逐个找的,而是每次移动一个二进制为,例如传入1的时候,接下来就是2、4、8、16这些位置,找到合适的位置放下即可,顺序未必是完全有序的,它只需要看到距离调度部分的越近的是有序性越强的时候就可以了,这样即可以保证一定的顺序性,达到较好的性能。
 

通过以上源码,尤其是大循环内的逻辑解释,可以得出如下结论:

1、由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。

2、如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。

3、Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。


更好的替代方法

        由于Timer存在上面说的这些缺陷,在JDK1.5中,我们可以使用ScheduledThreadPoolExecutor来代替它,使用Executors.newScheduledThreadPool工厂方法或使用ScheduledThreadPoolExecutor的构造函数来创建定时任务,它是基于线程池的实现,不会存在Timer存在的上述问题,当线程数量为1时,它相当于Timer。

        多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。

        由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。

彩蛋:

        虽然Timer有很多缺点,但是在分析代码的过程中,发现有一些编码的思想值得学习。

1. 在做循环逻辑的时候,可以更多的使用wait和notify做多线程的交互,减少无用的CPU消耗

2. 在移除某个任务的时候,把列表最后一个任务覆盖到待删除任务的位置,然后最后一个引用被置为null,极大减小了操作的时间复杂度。

void removeMin() {queue[1] = queue[size];queue[size--] = null; // Drop extra reference to prevent memory leakfixDown(1);
}

3. TaskQueue实现了优先队列的数据结构,内部是一个数组,数组内容实际上是从下标1开始填充的;它其实是用balanced binary heap来表示的,设父节点是queue[n],则它的两个字节点分别是queue[2*n]queue[2*n+1]。在合适的场景使用合适的数据结构,可以更小成本的实现场景需求。

        

参考:

https://www.cnblogs.com/heqiyoujing/p/10416065.html

https://blog.csdn.net/yaomingyang/article/details/82113216

https://blog.csdn.net/xieyuooo/article/details/8607220

https://blog.csdn.net/lfsf802/article/details/41621731

https://blog.csdn.net/xieyuooo/article/details/8607220/

https://segmentfault.com/a/1190000009246096


推荐阅读
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 我所理解的JMM 2 new原子性
    概述文本探讨构造函数是否为原子性问题。案例我们首先如下代码:publicclassPerson{publicintage;publicPerson(){age ... [详细]
  • 第七课主要内容:多进程多线程FIFO,LIFO,优先队列线程局部变量进程与线程的选择线程池异步IO概念及twisted案例股票数据抓取 ... [详细]
  • JVM:33 如何查看JVM的Full GC日志
    1.示例代码packagecom.webcode;publicclassDemo4{publicstaticvoidmain(String[]args){byte[]arr ... [详细]
author-avatar
你我她啊
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有