深入了解Java定时器中的Timer的原理

目录
  • 主要成员变量
  • 定时功能
  • TimerThread
  • 结论
  • Demo代码位置

Java在1.3版本引入了Timer工具类,它是一个古老的定时器,搭配TimerTask和TaskQueue一起使用。从Java5开始在并发包中引入了另一个定时器
ScheduledThreadPoolExecutor,它对Timer做了很多改进并提供了更多的工具,可以认为是对Timer的取代。

那为什么还要介绍Timer工具类呢?通过了解Timer的功能和它背后的原理,有助于我们更好的对比了解
ScheduledThreadPoolExecutor,同时ScheduledThreadPoolExecutor的一些改进思想在我们平时的编码工作中也可以借鉴。

主要成员变量

Timer中用到的主要是两个成员变量:

  • TaskQueue:一个按照时间优先排序的队列,这里的时间是每个定时任务下一次执行的毫秒数(相对于1970年1月1日而言)
  • TimerThread:对TaskQueue里面的定时任务进行编排和触发执行,它是一个内部无限循环的线程。
//根据时间进行优先排序的队列
private final TaskQueue queue = new TaskQueue();

//消费线程,对queue中的定时任务进行编排和执行
private final TimerThread thread = new TimerThread(queue);

//构造函数
public Timer(String name) {
        thread.setName(name);
        thread.start();
}

定时功能

Timer提供了三种定时模式:

  • 一次性任务
  • 按照固定的延迟执行(fixed delay)
  • 按照固定的周期执行(fixed rate)

第一种比较好理解,即任务只执行一次;针对第一种,Timer提供了以下两个方法:

//在当前时间往后delay个毫秒开始执行
public void schedule(TimerTask task, long delay) {...}
//在指定的time时间点执行
public void schedule(TimerTask task, Date time) {...}

第二种Fixed Delay模式也提供了以下两个方法

//从当前时间开始delay个毫秒数开始定期执行,周期是period个毫秒数
public void schedule(TimerTask task, long delay, long period) {...}
////从指定的firstTime开始定期执行,往后每次执行的周期是period个毫秒数
public void schedule(TimerTask task, Date firstTime, long period){...}

它的工作方式是:

第一次执行的时间将按照指定的时间点执行(如果此时TimerThread不在执行其他任务),如有其他任务在执行,那就需要等到其他任务执行完成才能执行。

从第二次开始,每次任务的执行时间是上一次任务开始执行的时间加上指定的period毫秒数。

如何理解呢,我们还是看代码

public static void main(String[] args) {
        TimerTask task1 = new DemoTimerTask("Task1");
        TimerTask task2 = new DemoTimerTask("Task2");
        Timer timer = new Timer();
        timer.schedule(task1, 1000, 5000);
        timer.schedule(task2, 1000, 5000);
}

static class DemoTimerTask extends TimerTask {
        private String taskName;
        private DateFormat df = new SimpleDateFormat("HH:mm:ss---");

        public DemoTimerTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public void run() {
            System.out.println(df.format(new Date()) + taskName + " is working.");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(df.format(new Date()) + taskName + " finished work.");
        }
}

task1和task2是几乎同时执行的两个任务,而且执行时长都是2秒钟,如果此时我们把第六行注掉不执行,我们将得到如下结果(和第三种Fixed Rate模式结果相同):

13:42:58---Task1 is working.
13:43:00---Task1 finished work.
13:43:03---Task1 is working.
13:43:05---Task1 finished work.
13:43:08---Task1 is working.
13:43:10---Task1 finished work.

如果打开第六行,我们再看下两个任务的执行情况。我们是期望两个任务能够同时执行,但是Task2是在Task1执行完成后才开始执行(原因是TimerThread是单线程的,每个定时任务的执行也在该线程内完成,当多个任务同时需要执行时,只能是阻塞了),从而导致Task2第二次执行的时间是它上一次执行的时间(13:43:57)加上5秒钟(13:44:02)。

13:43:55---Task1 is working.
13:43:57---Task1 finished work.
13:43:57---Task2 is working.
13:43:59---Task2 finished work.
13:44:00---Task1 is working.
13:44:02---Task1 finished work.
13:44:02---Task2 is working.
13:44:04---Task2 finished work.

那如果此时还有个Task3也是同样的时间点和间隔执行会怎么样呢?

结论是:也将依次排队,执行的时间依赖两个因素:

1.上次执行的时间

2.期望执行的时间点上有没有其他任务在执行,有则只能排队了

我们接下来看下第三种Fixed Rate模式,我们将上面的代码稍作修改:

public static void main(String[] args) {
        TimerTask task1 = new DemoTimerTask("Task1");
        TimerTask task2 = new DemoTimerTask("Task2");

        Timer timer = new Timer();
        timer.scheduleAtFixedRate(task1, 1000, 5000);
        timer.scheduleAtFixedRate(task2, 1000, 5000);
}

static class DemoTimerTask extends TimerTask {
        private String taskName;
        private DateFormat df = new SimpleDateFormat("HH:mm:ss---");

        public DemoTimerTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public void run() {
            System.out.println(df.format(new Date()) + taskName + " is working.");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(df.format(new Date()) + taskName + " finished work.");
        }
}

Task1和Task2还是在相同的时间点,按照相同的周期定时执行任务,我们期望Task1能够每5秒定时执行任务,期望的时间点是:14:21:47-14:21:52-14:21:57-14:22:02-14:22:07,实际上它能够交替着定期执行,原因是Task2也会定期执行,并且对TaskQueue的锁他们是交替着拿的(这个在下面分析TimerThread源码的时候会讲到)

14:21:47---Task1 is working.
14:21:49---Task1 finished work.
14:21:49---Task2 is working.
14:21:51---Task2 finished work.
14:21:52---Task2 is working.
14:21:54---Task2 finished work.
14:21:54---Task1 is working.
14:21:56---Task1 finished work.
14:21:57---Task1 is working.
14:21:59---Task1 finished work.
14:21:59---Task2 is working.
14:22:01---Task2 finished work.

TimerThread

上面我们主要讲了Timer的一些主要源码及定时模式,下面我们来分析下支撑Timer的定时任务线程TimerThread。

TimerThread大概流程图如下:

TimerThread流程

源码解释如下:

private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // 如果queue里面没有要执行的任务,则挂起TimerThread线程
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    // 如果TimerThread被激活,queue里面还是没有任务,则介绍该线程的无限循环,不再接受新任务
                    if (queue.isEmpty())
                        break; 

                    long currentTime, executionTime;
                    // 获取queue队列里面下一个要执行的任务(根据时间排序,也就是接下来最近要执行的任务)
                    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;
                        // taskFired表示是否需要立刻执行线程,当task的下次执行时间到达当前时间点时为true
                        if (taskFired = (executionTime<=currentTime)) {
                            //task.period==0表示这个任务只需要执行一次,这里就从queue里面删掉了
                            if (task.period == 0) {
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                //针对task.period不等于0的任务,则计算它的下次执行时间点
                                //task.period<0表示是fixed delay模式的任务
                                //task.period>0表示是fixed rate模式的任务
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    // 如果任务的下次执行时间还没有到达,则挂起TimerThread线程executionTime - currentTime毫秒数,到达执行时间点再自动激活
                    if (!taskFired)
                        queue.wait(executionTime - currentTime);
                }
                // 如果任务的下次执行时间到了,则执行任务
                // 注意:这里任务执行没有另起线程,还是在TimerThread线程执行的,所以当有任务在同时执行时会出现阻塞
                if (taskFired)
                    // 这里没有try catch异常,当TimerTask抛出异常会导致整个TimerThread跳出循环,从而导致Timer失效
                    task.run();
            } catch(InterruptedException e) {
            }
        }
}

结论

通过上面的分析,我们可以得出以下结论:

  • Timer支持三种模式的定时任务(一次性任务,Fixed Delay模式,Fixed Rate模式)
  • Timer中的TimerThread是单线程模式,因此导致所有定时任务不能同时执行,可能会出现延迟
  • TimerThread中并没有处理好任务的异常,因此每个TimerTask的实现必须自己try catch防止异常抛出,导致Timer整体失效

Demo代码位置

TimerFixedRateDemo.java

TimerFixedDelayDemo.java

到此这篇关于深入了解Java定时器中的Timer的原理的文章就介绍到这了,更多相关Java Timer原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java定时器timer的使用方法代码示例

    1.首先肯定是容器一启动就要启动定时器,所以我们可以选择把定时器写在一个监听器里,容器一启动所以监听器也就跟着启动,然后定时器就可以工作了. 第一步,把自己写的监听器加到web.xml中: 第二步,写一个监听器,实现ServletContextListener接口: 第三步,写一个定时器,继承TimerTask,在复写的run()方法里写具体的业务逻辑. 第四步,在自己的监听器里复写的 public void contextInitialized(ServletContextEvent arg0

  • Java多线程定时器Timer原理及实现

    前言 定时/计划功能在Java应用的各个领域都使用得非常多,比方说Web层面,可能一个项目要定时采集话单.定时更新某些缓存.定时清理一批不活跃用户等等.定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程方式进行处理,所以它和多线程技术关联还是相当大的.那和ThreadLocal一样,还是先讲原理再讲使用,Timer的实现原理不难,就简单扫一下就好了. Timer的schedule(TimeTask task, Date time)的使用 该方法的作用是在执行的日期执行一

  • Java定时器Timer简述

    概述 主要用于Java线程里指定时间或周期运行任务.Timer是线程安全的,但不提供实时性(real-time)保证. 构造函数 Timer() 默认构造函数. Timer(boolean) 指定关联线程是否作为daemon线程. Timer(String) 指定关联线程的名称. Timer(String, boolean) 同时指定关联线程的名称和是否作为daemon. schdule方法 schedule(TimerTask task, long delay) 以当前时间为基准,延迟指定的毫

  • Java线程Timer定时器用法详细总结

    定时/计划功能主要使用的就是Timer对象,它在内部还是使用多线程的方式进行处理,所以它和线程技术还是有非常大的关联. Timer类主要作用就是设置计划任务,但封装任务的类却是TimerTask类.TimerTask类是一个抽象类. 执行任务的时间晚于当前时间-----在未来执行的效果 import java.util.Date; import java.util.TimerTask; public class MyTask extends TimerTask{ @Override public

  • java中Timer定时器的使用和启动方式

    目录 Timer定时器的使用和启动 1.概述 2.应用场景 3.使用方法 4.启动方法 java的几种定时器小结 1.@Scheduled注解 2.quartz 3.使用Timer 4.使用线程控制 Timer定时器的使用和启动 1.概述 定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和多线程技术还是有非常大的关联的.在JDK中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务,但封装任务的类却是TimerTask类. 2

  • Java定时器Timer使用方法详解

    一.概念 定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和多线程技术还是有非常大的关联的.在JDK中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务,但封装任务的类却是TimerTask类. 通过继承 TimerTask 类 并实现 run() 方法来自定义要执行的任务: public class Mytask extends TimerTask { @Override public void run() { DateF

  • Java中的定时器Timer详解

    目录 总结 简单来说,定时器就相当于一个"闹钟",给定时器设定一个任务,约定这个任务在xxx时间之后执行~ Timer类提供了一个核心接口,schedule(安排) 指定一个任务交给定时器,在一定时间之后再去执行这个任务~ 如何实现定时器的效果~ Timer中要包含一个Task类,每个Task就表示一个具体的任务实例,Task里面包含一个时间戳(啥时候执行这个任务),还包含一个Runnable实例(用来表示任务具体是啥). Timer里面通过一个带优先级的阻塞队列,来组织如干个task

  • 深入了解Java定时器中的Timer的原理

    目录 主要成员变量 定时功能 TimerThread 结论 Demo代码位置 Java在1.3版本引入了Timer工具类,它是一个古老的定时器,搭配TimerTask和TaskQueue一起使用.从Java5开始在并发包中引入了另一个定时器ScheduledThreadPoolExecutor,它对Timer做了很多改进并提供了更多的工具,可以认为是对Timer的取代. 那为什么还要介绍Timer工具类呢?通过了解Timer的功能和它背后的原理,有助于我们更好的对比了解ScheduledThre

  • java并发中DelayQueue延迟队列原理剖析

    介绍 DelayQueue队列是一个延迟队列,DelayQueue中存放的元素必须实现Delayed接口的元素,实现接口后相当于是每个元素都有个过期时间,当队列进行take获取元素时,先要判断元素有没有过期,只有过期的元素才能出队操作,没有过期的队列需要等待剩余过期时间才能进行出队操作. 源码分析 DelayQueue队列内部使用了PriorityQueue优先队列来进行存放数据,它采用的是二叉堆进行的优先队列,使用ReentrantLock锁来控制线程同步,由于内部元素是采用的Priority

  • 详解Java String中intern方法的原理与使用

    目录 简介 常量池简介 intern方法简介(JDK7) 原理(JDK6与JDK7) 例程测试 例程分析 jdk1.6 jdk1.7 应用实例 简介 本文介绍Java的String的intern方法的原理. 常量池简介 在 JAVA 语言中有8种基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池(在方法区)的概念.常量池就类似一个JAVA系统级别提供的缓存.8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊. Str

  • java虚拟机中栈的运行知识点总结

    运行原理 1.不同线程中所包含的栈帧是不允许存在相互引用的. 2.如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给当前一个栈针,并且虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧. 3.Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令:另一种是抛出异常.不管使用哪种方式,都会导致栈帧被弹出. 实例 public class StackFrameTest { public static void main(String[] args) {

  • Java 定时器(Timer,TimerTask)详解及实例代码

     Java 定时器 在JAVA中实现定时器功能要用的二个类是Timer,TimerTask Timer类是用来执行任务的类,它接受一个TimerTask做参数 Timer有两种执行任务的模式,最常用的是schedule,它可以以两种方式执行任务:1:在某个时间(Data),2:在某个固定的时间之后(int delay).这两种方式都可以指定任务执行的频率,本文有二个例子,一个是简单的一个是用了内部类 1.简单实例 先写一个类 public class TimeTest { public stat

  • Java定时器通信协议管理模块Timer详解

    目录 Timer详解 定时功能 执行一次 Fixed Delay模式 Timer详解 Timer和TimerTask用于在后台线程中调度任务的java.util类.TimerTask负责任务的执行,Timer负责任务的调度. 定时功能 Timer提供了三种定时模式: 一次性任务 按照固定的延迟执行(fixed delay) 按照固定的周期执行(fixed rate) 执行一次 Timer提供了两种方法,应用于不同场景: //在当前时间往后delay个毫秒开始执行 public void sche

  • Python中threading.Timer()定时器实现定时任务

    目录 1.单线程执行 2.多线程执行 timer最基本理解就是定时器,可以启动多个定时任务,这些定时器任务是异步执行,所以不存在等待顺序执行问题. Timer方法 说明 Timer(interval, function, args=None, kwargs=None) 创建定时器 cancel() 取消定时器 start() 使用线程方式执行 join(self, timeout=None) 等待线程执行结束 1.单线程执行 示例代码: from datetime import datetime

  • java编程中自动拆箱与自动装箱详解

    什么是自动装箱拆箱 基本数据类型的自动装箱(autoboxing).拆箱(unboxing)是自J2SE 5.0开始提供的功能. 一般我们要创建一个类的对象实例的时候,我们会这样: Class a = new Class(parameter); 当我们创建一个Integer对象时,却可以这样: Integer i = 100; (注意:不是 int i = 100; ) 实际上,执行上面那句代码的时候,系统为我们执行了:Integer i = Integer.valueOf(100); (感谢@

  • Java定时器例子_动力节点Java学院整理

    Timer类是用来执行任务的类,它接受一个TimerTask做参数 ,这两个类使用起来非常方便,可以完成我们对定时器的绝大多数需求 . Timer有两种执行任务的模式,最常用的是schedule,它可以以两种方式执行任务: 1:在某个时间(Data), 2:在某个固定的时间之后(int delay).这两种方式都可以指定任务执行的频率 TimerTest.Java: package com.bjpowernode.test; import java.io.IOException; import

随机推荐