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用法的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot多数据源的两种实现方式实例

    SpringBoot多数据源的两种实现方式实例

    最近在项目开发中,需要为一个使用MySQL数据库的SpringBoot项目,新添加一个PLSQL数据库数据源,下面这篇文章主要给大家介绍了关于SpringBoot多数据源的两种实现方式,需要的朋友可以参考下
    2022-04-04
  • Java 基于TCP Socket 实现文件上传

    Java 基于TCP Socket 实现文件上传

    这篇文章主要介绍了Java 基于TCP Socket 实现文件上传的示例代码,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-12-12
  • 分布式消息队列RocketMQ概念详解

    分布式消息队列RocketMQ概念详解

    RocketMQ 是阿里开源的分布式消息中间件,跟其它中间件相比,RocketMQ 的特点是纯JAVA实现,是一套提供了消息生产,存储,消费全过程API的软件系统,本文详细介绍了分布式消息队列RocketMQ概念,需要的朋友可以参考下
    2023-05-05
  • 全面解析java final关键字

    全面解析java final关键字

    这篇文章主要介绍了java final关键字的使用,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2021-01-01
  • Kotlin中的抽象类实现

    Kotlin中的抽象类实现

    这篇文章主要介绍了Kotlin中的抽象类实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • JVM优先级线程池做任务队列的实现方法

    JVM优先级线程池做任务队列的实现方法

    这篇文章主要介绍了JVM优先级线程池做任务队列的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Spring MVC  接受请求参数的方法

    Spring MVC  接受请求参数的方法

    了解HTTP请求的GET和POST方法中如何携带参数,以及SpringMVC中如何接收这些参数,GET方法通过URL传递参数,而POST方法通常在请求体中传递,SpringMVC使用注解如@RequestParam和@RequestBody来绑定参数到控制器方法
    2024-09-09
  • 使用Java后台实现弹出框页面的代码案例

    使用Java后台实现弹出框页面的代码案例

    在现代Web应用中,弹出框(Modal)是一个常见且重要的UI组件,通过弹出框,我们可以实现用户交互、表单提交、信息提示等功能,本篇博客将详细介绍如何使用Java后台实现弹出框页面,并展示具体的代码案例和运行效果,需要的朋友可以参考下
    2024-08-08
  • Java Selenium实现多窗口切换的示例代码

    Java Selenium实现多窗口切换的示例代码

    这篇文章主要介绍了Java Selenium实现多窗口切换的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Lombok同时使⽤@Data和@Builder踩坑总结

    Lombok同时使⽤@Data和@Builder踩坑总结

    这篇文章主要介绍了Lombok同时使⽤@Data和@Builder踩坑总结,文章围绕主题展开详细的内容介绍,具有一定的参考价值需要的小伙伴可以参考一下,希望对你的学习有所帮助
    2022-05-05

最新评论