Java Timer使用讲解

目录
  • Timer 详解
    • 定时功能
  • 一、一次性任务
  • 二、可重复执行任务
  • 三、固定延时和固定速率区别(重点)
    • 1. 介绍
    • 2. 固定速率
    • 3. 固定延时
    • 4. 其他要点
  • 四、调度多个TimerTask
  • 五、取消任务

Timer 详解

Timer 和 TimerTask 用于在后台线程中调度任务的 java.util 类。 TimerTask 负责任务的执行, Timer 负责任务的调度。

定时功能

Timer 提供了三种定时模式:

fixed delay
fixed rate

java.util包下提供了对定时任务的支持,涉及2个类:

  1. Timer:定时器类
  2. TimerTask:任务抽象类

使用该定时任务我们需要继承TimerTask抽象类,覆盖run方法编写任务执行代码,并利用Timer定时器对TimerTask进行调度。

编写一个任务:

TimerTask task = new TimerTask() {
    @Override
    public void run() {
        System.out.println(DateUtil.formatNow() + " " + Thread.currentThread().getName() + " task run ");
    }
};

接着使用Timer对TimerTask进行调度,Timer提供了多种方法,可分为一次性任务和可重复执行任务。

一、一次性任务

一次性任务是指Timer执行一次之后,该任务后续不再执行。

一次性任务包括2个方法,如下:

  1. voidschedule(TimerTasktask,longdelay):延迟delay毫秒后执行一次task
  2. voidschedule(TimerTasktask,Datetime):在指定时间time执行一次task,如果time过期,将会立即执行

二、可重复执行任务

可重复执行任务是指,任务允许按照设定的规则重复执行。

可重复执行任务共有4个方法,分为固定延时 schedule和固定速率 scheduleAtFixedRate:

  1. voidschedule(TimerTasktask,longdelay,longperiod):延迟delay毫秒后执行task,之后每隔period毫秒执行一次task
  2. voidschedule(TimerTasktask,DatefirstTime,longperiod):在指定时间time执行一次task,之后每隔period毫秒执行一次task
  3. voidscheduleAtFixedRate(TimerTasktask,longdelay,longperiod):延迟delay毫秒后执行task,之后每隔period毫秒执行一次task
  4. voidscheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod):在指定时间time执行一次task,之后每隔period毫秒执行一次task

示例1:schedule方法,延迟delay毫秒后执行task,之后每隔period毫秒执行一次task

System.out.println("启动于:" + DateUtil.formatNow());
Timer timer = new Timer("timer");
timer.schedule(task, 1000, 2000);

输出:

启动于:2022-10-31 10:05:15
2022-10-31 10:05:16 timer task run 
2022-10-31 10:05:18 timer task run 
2022-10-31 10:05:20 timer task run

示例2:schedule在指定时间time执行一次task,之后每隔period毫秒执行一次task

System.out.println("启动于:" + DateUtil.formatNow());
Timer timer = new Timer("timer");
timer.schedule(task, DateUtil.parse("2022-10-31 10:07:00", DateUtil.YYYY_MM_DD_HH24_MM_SS), 2000);

输出:

启动于:2022-10-31 10:06:39
2022-10-31 10:07:00 timer task run 
2022-10-31 10:07:02 timer task run 
2022-10-31 10:07:04 timer task run

固定延时 schedule 和 固定速率 scheduleAtFixedRate 在正常情况下看起来功能基本是一致的,区别在于当任务耗时超出执行时间间隔period,后续任务被延误时,schedule和scheduleAtFixedRate的处理方式不同,后面介绍。

三、固定延时和固定速率区别(重点)

1. 介绍

由于Timer内部仅维护一个线程来执行所有任务,所以当前一个任务耗时过长,可能会导致后一个任务的执行被延误。

出现任务延误的情况下,固定延时 schedule和 固定速率 scheduleAtFixedRate 的区别就在于,schedule会顺延,而scheduleAtFixedRate会把延误任务立马补上。

在网上看到几个非常恰当的例子,贴上来加深理解。

例1:

暑假到了老师给schedule和scheduleAtFixedRate两个同学布置作业。

老师要求学生暑假每天写2页,30天后完成作业。

这两个学生每天按时完成作业,直到第10天,出了意外,两个学生出去旅游花了5天时间,这5天时间里两个人都没有做作业。任务被拖延了。

这时候两个学生采取的策略就不同了:

schedule重新安排了任务时间,旅游回来的第一天做第11天的任务,第二天做第12天的任务,最后完成任务花了35天。

scheduleAtFixedRate是个守时的学生,她总想按时完成老师的任务,于是在旅游回来的第一天把之前5天欠下的任务以及第16天当天的任务全部完成了,之后还是按照老师的原安排完成作业,最后完成任务花了30天。

例2:

固定速率就好比你今天加班到很晚,但是到了第二天还必须准点到公司上班,如果你一不小心加班到了第二天早上 9 点,你就连休息的时间都没有了。

而固定时延的意思是你必须睡够 8 个小时再过来上班,如果你加班到凌晨 6 点,那就可以下午过来上班了。

固定速率强调准点,固定时延强调间隔。

如果任务必须每天准点调度,那就应该使用固定速率调度,并且要确保每个任务执行时间不要太长,避免超过period间隔。

如果任务需要每隔几分钟跑一次,那就使用固定时延调度,它不是很在乎单个任务要跑多长时间。

我们来模拟一下这个情况。

首先,我们对TimerTask进行修改,让它某一次任务产生大量耗时:

TimerTask task = new TimerTask() {
    private int i = 1;
    @Override
    public void run() {
        System.out.print(i + " " + DateUtil.formatNow() + " 开始执行, ");
        if(i == 3) {
            ThreadUtil.sleep(11 * 1000);
        }
        System.out.println(DateUtil.formatNow() + " 结束");
        i++;
    }
};

该任务在执行第3次时,将会休眠11秒,这将会导致延误后续的任务。

2. 固定速率

示例:

Timer timer = new Timer("timer");
timer.scheduleAtFixedRate(task, 5000, 2000);

设定任务延迟5秒后执行第1次任务,之后每2秒执行一次。

输出:

启动于:2022-10-31 15:51:24
1 2022-10-31 15:51:29 开始执行, 2022-10-31 15:51:29 结束
2 2022-10-31 15:51:31 开始执行, 2022-10-31 15:51:31 结束
3 2022-10-31 15:51:33 开始执行, 2022-10-31 15:51:44 结束 *
4 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
5 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
6 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
7 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
8 2022-10-31 15:51:44 开始执行, 2022-10-31 15:51:44 结束 *
9 2022-10-31 15:51:45 开始执行, 2022-10-31 15:51:45 结束
10 2022-10-31 15:51:47 开始执行, 2022-10-31 15:51:47 结束
11 2022-10-31 15:51:49 开始执行, 2022-10-31 15:51:49 结束

如果不存在第3次耗时11秒的情况下,正常任务执行时间应该为:

启动于:2022-10-31 15:51:24
1 2022-10-31 15:51:29 开始执行, 2022-10-31 15:51:29 结束
2 2022-10-31 15:51:31 开始执行, 2022-10-31 15:51:31 结束
3 2022-10-31 15:51:33 开始执行, 2022-10-31 15:51:33 结束 *
4 2022-10-31 15:51:35 开始执行, 2022-10-31 15:51:35 结束 *
5 2022-10-31 15:51:37 开始执行, 2022-10-31 15:51:37 结束 *
6 2022-10-31 15:51:39 开始执行, 2022-10-31 15:51:39 结束 *
7 2022-10-31 15:51:41 开始执行, 2022-10-31 15:51:41 结束 *
8 2022-10-31 15:51:43 开始执行, 2022-10-31 15:51:43 结束 *
9 2022-10-31 15:51:45 开始执行, 2022-10-31 15:51:45 结束
10 2022-10-31 15:51:47 开始执行, 2022-10-31 15:51:47 结束
11 2022-10-31 15:51:49 开始执行, 2022-10-31 15:51:49 结束

但是在第3次执行任务时因为执行耗时11秒,第4次本该在15:51:35开始执行并完成任务,却到了15:51:44才执行完成,这11秒延误了后续5个任务的正常执行,因此在15:51:44时,scheduleAtFixedRate赶作业把延误的5个任务一起执行了。

最后赶上了原本的进度,第9个任务准时在15:51:45执行。

3. 固定延时

示例:

Timer timer = new Timer("timer");
timer.schedule(task, 5000, 2000);

输出:

启动于:2022-10-31 15:56:59
1 2022-10-31 15:57:04 开始执行, 2022-10-31 15:57:04 结束
2 2022-10-31 15:57:06 开始执行, 2022-10-31 15:57:06 结束
3 2022-10-31 15:57:08 开始执行, 2022-10-31 15:57:19 结束 *
4 2022-10-31 15:57:19 开始执行, 2022-10-31 15:57:19 结束
5 2022-10-31 15:57:21 开始执行, 2022-10-31 15:57:21 结束
6 2022-10-31 15:57:24 开始执行, 2022-10-31 15:57:24 结束
7 2022-10-31 15:57:26 开始执行, 2022-10-31 15:57:26 结束
8 2022-10-31 15:57:28 开始执行, 2022-10-31 15:57:28 结束
9 2022-10-31 15:57:30 开始执行, 2022-10-31 15:57:30 结束
10 2022-10-31 15:57:32 开始执行, 2022-10-31 15:57:32 结束

如果不存在第3次耗时11秒的情况下,正常任务执行时间应该为:

启动于:2022-10-31 15:56:59
1 2022-10-31 15:57:04 开始执行, 2022-10-31 15:57:04 结束
2 2022-10-31 15:57:06 开始执行, 2022-10-31 15:57:06 结束
3 2022-10-31 15:57:08 开始执行, 2022-10-31 15:57:08 结束 *
4 2022-10-31 15:57:10 开始执行, 2022-10-31 15:57:10 结束
5 2022-10-31 15:57:12 开始执行, 2022-10-31 15:57:12 结束
6 2022-10-31 15:57:14 开始执行, 2022-10-31 15:57:14 结束
7 2022-10-31 15:57:16 开始执行, 2022-10-31 15:57:16 结束
8 2022-10-31 15:57:18 开始执行, 2022-10-31 15:57:18 结束
9 2022-10-31 15:57:20 开始执行, 2022-10-31 15:57:20 结束
10 2022-10-31 15:57:22 开始执行, 2022-10-31 15:57:22 结束

使用schedule调度,第4次任务本该在15:57:10开始执行,但由于耗时11秒直到15:57:19才开始。

而第3次任务实际是在19秒完成,完成后又在19秒立即执行第4次,中间少了2秒间隔,第4次完成后接着开始2秒一次,变为了从21秒开始执行第5次。

和我原本的推测不一样的是,本以为19秒完成后,第4次会隔2秒在21秒执行,没想到19秒会立即执行。

猜测与delay参数有关,但调整了delay后仍然一样,完成的那一秒还是会马上再执行第4次任务。

通过以上测试对比,我们可以感受到Timer中固定速率和固定延时的区别,但为了避免出错,使用Timer时应让TimerTask耗时尽可能短。

4. 其他要点

1.以上是仅第3次任务加上了耗时11秒,如果是所有任务都耗时11秒呢?

如果每次任务执行都耗时11秒,那么无论是固定速率还是固定延时,都将是11秒执行一个任务。

2.如果改为schedule(TimerTasktask,DatefirstTime,longperiod)和scheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod)来调度任务,firstTime指定为10点,而当前系统时间为11点,会出现什么情况呢?

虽然firstTime已经过期,但是Timer将会立即开始执行任务,之后按照period间隔重复执行任务。

3.如果TimerTask执行过程中抛出了异常会发生什么事情?

Timer内部仅维护一个线程,当任一TimerTask抛出异常,将导致此线程终止运行,该Timer负责的所有任务都无法执行。

四、调度多个TimerTask

在上一节中,介绍的是一个可重复执行的TimeTask,如果执行耗时大于设定的间隔period,将会影响该TimerTask下一次执行的时间点。

而这一节则是为了单独说明,一个Timer同时调度多个TimeTask也会互相影响。

示例:

TimerTask task1 = new TimerTask() {
    private int i = 1;
    @Override
    public void run() {
        System.out.print(i + " task1:" + DateUtil.formatNow() + " 开始执行, ");
        ThreadUtil.sleep(11 * 1000);
        System.out.println(DateUtil.formatNow() + " 结束");
        i++;
    }
};
TimerTask task2 = new TimerTask() {
    private int i = 1;
    @Override
    public void run() {
        System.out.print(i + "  task2:" + DateUtil.formatNow() + " 开始执行, ");
        ThreadUtil.sleep(11 * 1000);
        System.out.println(DateUtil.formatNow() + " 结束");
        i++;
    }
};

Timer timer = new Timer("timer");
timer.scheduleAtFixedRate(task1, 5000, 2000);
timer.scheduleAtFixedRate(task2, 5000, 2000);

输出:

1 task1:2022-10-31 16:58:27 开始执行, 2022-10-31 16:58:38 结束
1 task2:2022-10-31 16:58:38 开始执行, 2022-10-31 16:58:49 结束
2 task2:2022-10-31 16:58:49 开始执行, 2022-10-31 16:59:00 结束
2 task1:2022-10-31 16:59:00 开始执行, 2022-10-31 16:59:11 结束
3 task1:2022-10-31 16:59:11 开始执行, 2022-10-31 16:59:22 结束
3 task2:2022-10-31 16:59:22 开始执行, 2022-10-31 16:59:33 结束
4 task2:2022-10-31 16:59:33 开始执行, 2022-10-31 16:59:44 结束
4 task1:2022-10-31 16:59:44 开始执行, 2022-10-31 16:59:55 结束

可以发现,task1和task2其实都没有按照既定时间去执行任务了。

根本原因是在于,Timer内部仅维护一个线程执行所有TimerTask,为了避免错误,一个Timer对象最好仅调度一个TimerTask对象,除非可以确保多个TimerTask之间一定不会相互影响。

因此编写TimerTask时应当自行捕获异常。

五、取消任务

Timer在创建时实际上是默认在内部维护了一个非守护线程,即使任务全部执行完成,线程也并不会销毁。

Timer提供cancel()方法,可以手动调用取消定时器所有的任务,并销毁定时器。

如果想要Timer内部创建的是守护线程,可以使用以下构造方法创建定时器,设置isDaemon为true:

  • Timer(booleanisDaemon)

Timer(Stringname,booleanisDaemon)

如果没有自己定义name参数,默认Timer内部自动命名为“Timer-递增序号”,作为内部线程的线程名称,在构造方法内启动此线程。

如果是要取消单个任务,可以使用TimerTask的cancel()方法。

当TimerTask调用cancel之后,任务是取消了,但Timer自身并不能马上知道TimerTask被取消,而是在准备执行前才知道,因此Timer内部还维护着这个任务的引用。若希望Timer立即清除引用,可调用Timer.purge()立即执行清除。

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

(0)

相关推荐

  • 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使用TimerTask定时器获取指定网络数据

    复制代码 代码如下: import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.URL;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Timer;import java.util.TimerTask; public class GetYinInf

  • Java 定时器(Timer)及线程池里使用定时器实例代码

    java Timer定时器 简单实例代码: public class Test { public static void main(String[] args) { // Timer定时器 Timer mTimer = new Timer(); MyTack myTack = new MyTack(); mTimer.schedule(myTack, 2000, 3000);//第一个参数是需要执行的任务 第二个参数是延迟多少时间最开始执行,第三个参数是执行完后多少时间后进行再次执行是一个周期性

  • java定时任务Timer和TimerTask使用详解

    timer和timertask是jdk自带的定时任务实现,无需导入第三方jar包来完成 1.指定多久之后执行此任务,注意:只会执行一次 public class TimerTest { Timer timer; public TimerTest(int time){ timer = new Timer(); timer.schedule(new timerTaskTest(),time*1000);//timer.schedule(执行的方法,延迟多久执行(ms)) } public stati

  • Java Timer使用讲解

    目录 Timer 详解 定时功能 一.一次性任务 二.可重复执行任务 三.固定延时和固定速率区别(重点) 1. 介绍 2. 固定速率 3. 固定延时 4. 其他要点 四.调度多个TimerTask 五.取消任务 Timer 详解 Timer 和 TimerTask 用于在后台线程中调度任务的 java.util 类. TimerTask 负责任务的执行, Timer 负责任务的调度. 定时功能 Timer 提供了三种定时模式: fixed delay fixed rate java.util包下

  • java Timer测试定时调用及固定时间执行代码示例

    本文实例主要进行java Timer(定时调用.固定时间执行)测试,具体实现代码如下. 测试1 当任务执行时间小于重复执行的间隔时间 代码: public class TimerTest2 { public static void main(String[] args) throws InterruptedException { Timer timer = new Timer(); timer.schedule(new MyTask(0), 1000, 10000); //timer.sched

  • Java定时任务:利用java Timer类实现定时执行任务的功能

    一.概述 在java中实现定时执行任务的功能,主要用到两个类,Timer和TimerTask类.其中Timer是用来在一个后台线程按指定的计划来执行指定的任务. TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务,具体要执行的代码写在TimerTask需要被实现的run方法中. 二.先看一个最简单的例子 我们通过代码来说明 import java.text.SimpleDateFormat; import java.util.Date; import java.util.T

  • Java 多线程实例讲解(一)

    Java多线程(一) 多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程各重要知识点.掌握了上图中的各知识点,Java中的多线程也就基本上掌握了.主要包括: Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); 就绪状态(Runnable):当调用线程对象的

  • java Timer 定时每天凌晨1点执行任务

    下面给大家介绍java Timer 定时每天凌晨1点执行任务,具体代码如下所示: import java.util.TimerTask; /** * 执行内容 * @author admin_Hzw * */ public class Task extends TimerTask { public void run() { System.out.println("我有一头小毛驴!"); } } import java.util.Calendar; import java.util.Da

  • 详解JAVA Timer和TimerTask

    Timer和TimerTask可以做为实现线程的第三种方式,前两中方式分别是继承自Thread类和实现Runnable接口. Timer是一种线程设施,用于安排以后在后台线程中执行的任务.可安排任务执行一次,或者定期重复执行,可以看成一个定时器,可以调度TimerTask.TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力. 一个Timer可以调度任意多个TimerTask,它会将TimerTask存储在一个队列中,顺序调度,如果想两个TimerTask并发执行,

  • Java代理深入讲解之静态代理

    什么是代理 代理就是给目标对象一个代理对象,并由代理对象控制目标的引用. 为什么要使用代理模式 1.通过引入代理对象的方式,可以间接的访问目标对象,避免直接访问目标对象给系统带来不必要的复杂性. 2.通过代理对象可以对原有的业务进行业务增强处理. 举例:如果我们需要买国外的某一件商品A,这个时候我们一般有两个途径要么直接去国外买,要么可以找一些代购人员帮我们去购买.在这种情况下,我们由于直接去国外买,实在是太耗软妹币,而且还要花时间等等,这个时候我们最优的选择就是找代购购买,这样也帮我们省去了很

  • java中断机制实例讲解

    一.导言 线程A对线程B发出建议: 你好,可以停止了哟~ 在实际生产环境中,对于阻塞任务,可能存在一些情况导致阻塞任务取消.终止,例如: 计时器到期,I/O 完成,或者另一个线程的动作(释放一个锁,设置一个标志,或者将一个任务放在一个工作队列中).这种情况下可以使用java的中断机制来进行线程间通信. java线程中断的实现是基于一个称为中断状态的内部标志位来实现的,其中断的含义更像是建议,一个线程如何响应另一个线程的中断完全取决于程序员: 继续向上抛出.封装后抛出.中断状态复原.忽略等.jav

  • Java RMI机制讲解

    Java RMI Java RMI之HelloWorld篇 Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方法调用的任何对象必须实现该远程接口. Java RMI不是什么新技术(在Java1.1的时代都有了),但却是是非常重要的底层技术. 大名鼎鼎的EJB都是建立在rmi基础之上的,现在还有一些开源的远程调用组件,其底层技术也是rmi. 在大力鼓

  • java volatile案例讲解

    本篇来自java并发编程实战关于volatile的总结. 要说volatile,先得明白内存可见性.那我们就从内存可见性说起. 一.内存可见性 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.在单线程环境中,如果向某个变量先写入值,然后在没有其他写入操作的情况下读取这个变量,那么总能得到相同的值.这看起来很自然.然而,当读操作和写操作在不同的线程中执行时,情况却并非如此,这听起来或许有些难以接受.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能

随机推荐