Java定时/延时任务之Timer用法详解

 更新时间:2024年12月15日 10:37:12   作者:JWASX  
在 Java Development Kit (JDK) 中,java.util.Timer 是一个用于调度任务的工具类,本文主要来和大家聊聊Timer的用法,有需要的小伙伴可以了解下

1. 概要

上一篇文章地址:定时/延时任务-自己实现一个简单的定时器

在上一篇文章中,我们自己实现了一个简单的 Timer 并提出了一些缺点,下面我们就来看看 JDK 中的 Timer 用法

2. 简述

在 Java Development Kit (JDK) 中,java.util.Timer 是一个用于调度任务的工具类。Timer 类使用一个后台线程来遍历队列中的任务,同时可以按照固定的延时或者固定的速率来重复执行。

首先在介绍 Timer 的 API 之前,先来看两个概念:

2.1 固定速率

固定速率 策略表示任务在固定的时间间隔内重复执行,不管任务的执行时间有多长,如果任务的执行时间超过了时间间隔,那么下一个任务会在当前任务执行完毕之后就会马上开始执行,下面是一个例子:假设我们设置了一个固定速率为 5 的任务,从 0s 开始执行,也就是说这个任务 5s 执行一次:

  • 第一次执行: 在 0s 开始执行一次,假设执行时间是 3s
  • 第二次执行: 在 5s 开始执行第二次,假设执行的时候被阻塞了,执行了 8s
  • 第三次执行: 在 13s 开始执行第三次,假设执行的时候被阻塞了,执行了 3s
  • 第四次执行: 在 16s 开始执行第四次,假设执行的时候没有被阻塞
  • 第四次执行: 在 20s 开始执行第五次,假设执行的时候没有被阻塞

看了上面的过程分析,你可能有点懵,没关系,等到下面的时候会有例子并解释

2.2 固定延时

固定延时 策略表示任务在当前任务执行完成之后,固定延时一段时间再执行下一个任务,下面是一个例子:假设我们设置了一个固定速率为 5 的任务,从 0s 开始执行,也就是说这个任务 5s 执行一次:

  • 第一次执行: 在 0s 开始执行一次,假设执行时间是 3s
  • 第二次执行: 在 5s 开始执行第二次,假设执行的时候被阻塞了,执行了 8s
  • 第三次执行: 在 13s 开始执行第三次,假设执行的时候被阻塞了,执行了 3s
  • 第四次执行: 在 18s 开始执行第四次,假设执行的时候没有被阻塞
  • 第四次执行: 在 23s 开始执行第五次,假设执行的时候没有被阻塞

2.3 区别

看了上面两种方式的分析,可以做一个小的总结:

固定速率: 任务会按照固定时间间隔执行,如果任务执行的时间大于时间间隔,那么下一个任务会马上执行

固定延时: 任务会按照固定延时执行,如果任务执行的时间小于时间间隔,那么两次任务的执行时间间隔就是设置的延时;如果任务执行的时间大于时间间隔,那么两次任务执行的时间间隔就是任务执行的时间,也就是说下一次任务会马上执行

固定速率 这种方式比适合用于严格要求按照时间间隔执行的任务,比如心跳探测、数据收集等…

固定延时 执行任务的时间不固定,但是得确保每一次任务执行完之后有一定时间间隔再执行下一次的任务,比如日志收集、数据清理等…

3. Timer 的用法

下面我们就来介绍下 Timer 的几个 API 的用法

3.1 固定延时 - public void schedule(TimerTask task, long delay, long period)

这个 API 的意思是:延时 delay 后开始按照 period 的间隔执行

public class Pra {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
                    if(Math.random() < 0.5){
                        System.out.println("sleep: 3s");
                        Thread.sleep(3000);
                    } else {
                        System.out.println("sleep: 8s");
                        Thread.sleep(8000);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 5000);
    }

    private static String getTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return sdf.format(new Date());
    }

}

输出结果如下:

3.1.1 解释

解释:Timer 源码中对于任务的添加是线程被唤醒后获取到任务,之后就会立马计算出下一次该任务的调度时间加入队列中

有了上面的基础再来看输出,这就是为什么任务执行了 3s 最终还是在 49s 就开始执行下一个任务,因为 44s 执行任务的时候会根据当前执行时间算出下一个任务执行时间应该是 44 + 5 = 49s,而第二个任务 49s 执行的时候会算出下一个任务执行时间是 49 + 5 = 54s,但是由于任务执行时间长达 8s,导致下一个任务根本没时间被调度,所以只能在 57s 执行完之后去看队列,发现队列里面 54s 的任务早就到时间了,这时候算出下一个任务的执行时间是 57 + 5 = 02s,于是把这个任务加入到队列中,然后立马调度这个 54s 的任务,以此类推

你可能会有疑问:如果我任务执行时间是 8s,任务间隔是 3s,不会导致执行完一个任务之后队列中会有多个没有执行的任务吗?并不会,因为固定延时是按照当前时间来算下一个任务的计算时间,所以任务执行时间大于任务间隔时间的前提下,不管你间隔多少,都是以任务执行时间为主

但是对于固定速率又不一样了,这个我们下面会说

3.2 固定延时 - public void schedule(TimerTask task, Date firstTime, long period)

顾名思义,就是设置一个第一次启动的时间点,然后以 period 的延时执行,看下面的例子:

public class Pra {

    public static void main(String[] args) {
        Timer timer = new Timer();
        System.out.println("start time = " + getTime());
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
                    if(Math.random() < 0.5){
                        System.out.println("sleep: 3s");
                        Thread.sleep(3000);
                    } else {
                        System.out.println("sleep: 8s");
                        Thread.sleep(8000);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, new Date(System.currentTimeMillis() + 10000), 5000);
    }

    private static String getTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return sdf.format(new Date());
    }

}

上面 3.1 已经有解释了

3.3 固定速率 - public void scheduleAtFixedRate(TimerTask task, long delay, long period)

当前延时 delay 开始,接着每次执行的间隔是 period

public class Pra {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
                    if(Math.random() < 0.5){
                        System.out.println("sleep: 3s");
                        Thread.sleep(3000);
                    } else {
                        System.out.println("sleep: 8s");
                        Thread.sleep(8000);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 5000);
    }

    private static String getTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return sdf.format(new Date());
    }

}

3.3.1 解释

1.首先在 27s 开始执行第一次,然后往队列立马加入一个 32s 的下一次执行的任务,当前任务执行时间 3s

2. 第二次在 32s 开始执行第而次,然后往队列立马加入一个 37s 的下一次执行的任务,当前任务执行时间 8s

3. 第三次执行,当 工作线程 执行上一个任务之后已经到 40s 了,由于 40s 已经超过了 37s 的延时任务执行时间,于是会立马开始执行,这时候往队列里面添加一个 42s 执行的任务(下一次执行)

4. 第四次执行,当前任务执行时间 3s,执行完已经 43s 了,这时候执行结束会发现队列里面的 42s 的任务已经过期了,就会往队列立马添加一个 47s 的任务(下一次执行),然后立马开始执行,所以第四次执行时间是 43s

5. 第四次执行的时间是 3s ,执行完任务是 46s,这时候没到 47s,所以没有到下一次任务的执行时间,继续等待到 47s 执行 第五次任务

3.4 固定速率 - public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

和上面一样就是选择某个首次执行时间点开始执行,后续速率 period

public class Pra {

    public static void main(String[] args) {
        Timer timer = new Timer();
        System.out.println("start time = " + getTime());
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
                    if(Math.random() < 0.5){
                        System.out.println("sleep: 3s");
                        Thread.sleep(3000);
                    } else {
                        System.out.println("sleep: 8s");
                        Thread.sleep(8000);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, new Date(), 5000);
    }

    private static String getTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return sdf.format(new Date());
    }

}

3.5 非周期任务 - public void schedule(TimerTask task, Date time)

上面都是周期任务,下面这两个就是非周期任务,顾名思义就是只执行一次的任务,来看例子:

public class Pra {

    public static void main(String[] args) {
        Timer timer = new Timer();
        System.out.println("start time = " + getTime());
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
                    if(Math.random() < 0.5){
                        System.out.println("sleep: 3s");
                        Thread.sleep(3000);
                    } else {
                        System.out.println("sleep: 8s");
                        Thread.sleep(8000);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, new Date(System.currentTimeMillis() + 10000));
    }

    private static String getTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return sdf.format(new Date());
    }

}

3.6 非周期任务 - schedule(TimerTask task, long delay)

延迟 delay 时间之后开始执行

public class Pra {

    public static void main(String[] args) {
        Timer timer = new Timer();
        System.out.println("start time = " + getTime());
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
                    if(Math.random() < 0.5){
                        System.out.println("sleep: 3s");
                        Thread.sleep(3000);
                    } else {
                        System.out.println("sleep: 8s");
                        Thread.sleep(8000);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 10000);
    }

    private static String getTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return sdf.format(new Date());
    }

}

以上就是Java定时/延时任务之Timer用法详解的详细内容,更多关于Java Timer用法的资料请关注脚本之家其它相关文章!

相关文章

  • Java实现基于NIO的多线程Web服务器实例

    Java实现基于NIO的多线程Web服务器实例

    在本篇文章里小编给大家整理的是关于Java实现基于NIO的多线程Web服务器实例内容,需要的朋友们可以学习下。
    2020-03-03
  • java实习--每天打卡十道面试题!

    java实习--每天打卡十道面试题!

    临近秋招,备战暑期实习,祝大家每天进步亿点点!本篇文章准备了十道java的常用面试题,希望能够给大家提供帮助,最后祝大家面试成功,进入自己心仪的大厂
    2021-07-07
  • Mybatis使用foreach标签实现批量插入方式

    Mybatis使用foreach标签实现批量插入方式

    这篇文章主要介绍了Mybatis使用foreach标签实现批量插入方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Java 调整格式日志输出

    Java 调整格式日志输出

    本文主要介绍Java 的日志输出格式,在开发java的时候会经常看日志进行调试或者查看错误,这里给大家介绍日志输出调整格式,以便大家看日志的时候更加方便,
    2016-07-07
  • Java实现折半插入排序算法的示例代码

    Java实现折半插入排序算法的示例代码

    折半插入排序(Binary Insertion Sort)是对插入排序算法的一种改进。不断的依次将元素插入前面已排好序的序列中。本文将利用Java语言实现这一排序算法,需要的可以参考一下
    2022-08-08
  • jenkins持续自动化发布、集成实践

    jenkins持续自动化发布、集成实践

    文章描述了在两台服务器(10.1.1.13和10.1.1.14)上部署Jenkins和Tomcat的过程,包括环境准备、防火墙关闭、主机名解析、配置DNS、关闭SELinux、安装Java和Tomcat、配置Tomcat用户和访问权限、安装Jenkins、启动Jenkins服务以及配置Java项目
    2026-02-02
  • Java静态方法不具有多态性详解

    Java静态方法不具有多态性详解

    下面小编就为大家带来一篇Java静态方法不具有多态性详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • 基于SpringBoot+Beetl实现动态数据库DDL操作的实现指南

    基于SpringBoot+Beetl实现动态数据库DDL操作的实现指南

    你是否曾经为了应对频繁的业务变化而疲于修改数据库表结构?是否因为手动编写SQL脚本而感到枯燥乏味?本文,我要分享一个强大的技术组——SpringBoot + Beetl,它可以帮助我们实现动态数据库DDL操作,需要的朋友可以参考下
    2026-01-01
  • 出现java.util.ConcurrentModificationException 问题及解决办法

    出现java.util.ConcurrentModificationException 问题及解决办法

    这篇文章主要介绍了出现java.util.ConcurrentModificationException 问题及解决办法的相关资料,需要的朋友可以参考下
    2017-02-02
  • Springboot中@Value失效问题

    Springboot中@Value失效问题

    这篇文章主要介绍了Springboot中@Value失效问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-11-11

最新评论