当前位置: 首页 > news >正文

Java多线程(4):使用线程池执行定时任务

在现实世界里,我们总是免不了要定期去做一件事情(比如上课)—— 在计算机的世界里,更是如此。比如我们手机每天叫我们起床的电子闹钟,某些网站会定期向我们发送一些推荐相关的邮件,集群中我们需要每隔一定时间检查是否有机器宕机等。


使用线程池 中已经介绍,JDK 1.5 时,标准类库添加了对线程池的支持,然后在线程池核心实现 ThreadPoolExecutor 的基础上,实现了 ScheduledThreadPoolExecutor,作为可以 定时和周期性执行任务 的线程池。ScheduledThreadPoolExecutor 的类图如下:
ScheduledThreadPoolExecutor 的类图

ScheduledThreadPoolExecutor 实现了 ScheduledExecutorService 接口,ScheduledExecutorService 继承了 ExecutorService 接口,所以首先 ScheduledThreadPoolExecutor 是一个 ExecutorService (线程池),然后除了具有线程池的功能,它还有定时和周期性执行任务的功能。ScheduledExecutorService 除了从 ExecutorService 继承的方法外,还包括如下四个方法:
ScheduledExecutorService 定义的四个方法

第一个 Schedule 方法:
Schedule 方法

delay 指定的时间后,执行指定的 Runnable 任务,可以通过返回的 ScheduledFuture<?> 与该任务进行交互。

第二个 Schedule 方法:
第二个 Schedule 方法

delay 指定的时间后,执行指定的 Callable<V> 任务,可以通过返回的 ScheduledFuture<V> 与该任务进行交互。

ScheduledFuture 接口 继承自 Future 接口,所以 ScheduledFuture 和任务的交互方式与 Future 一致。所以通过ScheduledFuture,可以 判断定时任务是否已经完成,获得定时任务的返回值,或者取消任务等)

scheduleAtFixedRate 方法:
scheduleAtFixedRate 方法

initialDelay 指定的时间后,开始按周期 period 执行指定的 Runnable 任务。
假设调用该方法后的时间点为 0,那么第一次执行任务的时间点为 initialDelay,第二次为 initialDelay + period,第三次为 initialDelay + period + period,以此类推。

scheduleWithFixedDelay 方法:
scheduleWithFixedDelay 方法

initialDelay 指定的时间后,开始按指定的 delay 延期性的执行指定的 Runnable 任务。
假设调用该方法后的时间点为 0,每次任务需要耗时 T(i)i 为第几次执行任务),那么第一次执行任务的时间点为 initialDelay,第一次完成任务的时间点为 initialDelay + T(1),则第二次执行任务的时间点为 initialDelay + T(1) + delay;第二次完成任务的时间点为 initialDelay + (T(1) + delay) + T(2),所以第三次执行任务的时间点为 initialDelay + T(1) + delay + T(2) + delay,以此类推。


我们来实践下 ScheduledThreadPoolExecutorscheduleAtFixedRate 方法:

public class ScheduledExecutorServiceTest {

    public static void main(String[] args) throws Exception {
        ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
        
        TimerTask timerTask = new TimerTask(2000); // 任务需要 2000 ms 才能执行完毕

        System.out.printf("起始时间:%s\n\n", new SimpleDateFormat("HH:mm:ss").format(new Date()));

        // 延时 1 秒后,按 3 秒的周期执行任务
        timer.scheduleAtFixedRate(timerTask, 1000, 3000, TimeUnit.MILLISECONDS);
    }

    private static class TimerTask implements Runnable {

        private final int sleepTime;
        private final SimpleDateFormat dateFormat;

        public TimerTask(int sleepTime) {
            this.sleepTime = sleepTime;
            dateFormat = new SimpleDateFormat("HH:mm:ss");
        }

        @Override
        public void run() {
            System.out.println("任务开始,当前时间:" + dateFormat.format(new Date()));

            try {
                System.out.println("模拟任务运行...");
                Thread.sleep(sleepTime);
            } catch (InterruptedException ex) {
                ex.printStackTrace(System.err);
            }

            System.out.println("任务结束,当前时间:" + dateFormat.format(new Date()));
            System.out.println();
        }

    }
}

运行结果:
运行结果

可以看到运行结果完全符合预期 —— 延时 1 秒后,每隔 3 秒执行一次任务。


上面是任务的运行时间小于周期时间的情况 —— 那如果任务运行的时间大于给定的执行周期呢?(比如任务运行需要 3 s,但是我们指定的周期为 2 s)

修改 main 方法:

public static void main(String[] args) throws Exception {
    ScheduledExecutorService timer = Executors.newScheduledThreadPool(2);

    TimerTask timerTask = new TimerTask(3000); // 每个任务需要 3000 ms 才能执行完毕

    System.out.printf("起始时间:%s\n\n", new SimpleDateFormat("HH:mm:ss").format(new Date()));

    timer.scheduleAtFixedRate(timerTask, 1000, 2000, TimeUnit.MILLISECONDS);
}

运行结果:
运行结果

可以看到此时虽然我们指定的周期为 2 s,但是因为任务的运行就需要 3 s(超过周期),所以这种情况下 scheduleAtFixedRate 的处理方式为 上一次任务刚完成,则紧接着立即运行下一次任务,而不是使用线程池中的空闲线程来运行任务以维护 2 秒这个周期 —— 由此可见,每个定时任务在 ScheduledThreadPoolExecutor 中,都是串行运行的,即下一次运行任务一定在上一次任务结束之后。

scheduleWithFixedDelay 方法 的使用也十分简单,请有兴趣的读者自己实践)

相关文章:

  • SQL 收藏------------SQL操作全集
  • get方式和set方式提交时乱码
  • redis 主从配置
  • CIF,4CIF,QCIF,D1
  • javascript格式化日期
  • 取消开机硬盘检测
  • span - HTML元素
  • 数据库备份相关
  • 微信小程序正式发布,符合你的预期么?
  • Community Server 2008.5 SP2发布啦
  • Microsoft Office Communicator 2007R2中的彩蛋!!
  • git相关资料
  • 识人、用人、激人、留人、斩人
  • 逻辑思考之选择限定范围内的数量插入不指定位置并且具有替换功能
  • 一些零散知识收集
  • 【css3】浏览器内核及其兼容性
  • Asm.js的简单介绍
  • css选择器
  • css属性的继承、初识值、计算值、当前值、应用值
  • Gradle 5.0 正式版发布
  • java中的hashCode
  • Python语法速览与机器学习开发环境搭建
  • 编写符合Python风格的对象
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 关于extract.autodesk.io的一些说明
  • 老板让我十分钟上手nx-admin
  • 前端知识点整理(待续)
  • 如何利用MongoDB打造TOP榜小程序
  • 使用Swoole加速Laravel(正式环境中)
  • Java总结 - String - 这篇请使劲喷我
  • kubernetes资源对象--ingress
  • 阿里云重庆大学大数据训练营落地分享
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #define 用法
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (阿里云万网)-域名注册购买实名流程
  • (附源码)计算机毕业设计ssm电影分享网站
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (五)网络优化与超参数选择--九五小庞
  • (转)memcache、redis缓存
  • .net/c# memcached 获取所有缓存键(keys)
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • .net专家(高海东的专栏)
  • @Bean, @Component, @Configuration简析
  • @基于大模型的旅游路线推荐方案
  • [④ADRV902x]: Digital Filter Configuration(发射端)
  • [AIGC] Java 和 Kotlin 的区别
  • [BJDCTF2020]The mystery of ip1
  • [C#]OpenCvSharp使用帧差法或者三帧差法检测移动物体
  • [C#]winform利用seetaface6实现C#人脸检测活体检测口罩检测年龄预测性别判断眼睛状态检测
  • [HTML]Web前端开发技术30(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [IDF]聪明的小羊