详解 ScheduledExecutorService 的周期性执行方法

在最近的工作中,需要实现一个当一个任务执行完后,再等 100 毫秒然后再次执行的功能。当时最先反映到的就是 java 线程池的 ScheduledExecutorService,而 ScheduledExecutorService 有两个周期性执行任务的方法,分别是 scheduleAtFixedRate 与 scheduleWithFixedDelay,当时对这两个方法也不大了解,感觉和我的理解有所偏差,所以对这两个方法进行了研究。

ScheduledExecutorService 的基本原理

想要了解 scheduleWithFixedDelay 和 scheduleAtFixedRate 这两个周期性执行任务的方法,首先要了解 ScheduledExecutorService 的原理。在《java 并发编程的艺术》一书中有详细的解说,这里就简单的阐述一下。

ScheduledExecutorService 与其他线程池的区别,主要在于在执行前将任务封装为ScheduledFutureTask与其使用的阻塞队列DelayedWorkQueue。

ScheduledFutureTask

private class ScheduledFutureTask

extends FutureTask implements RunnableScheduledFuture {

/** 表示这个任务添加到ScheduledExecutorService中的序号 */

private final long sequenceNumber;

/** T表示这个任务将要被执行的具体时间(时间戳) */

private long time;

/**

* 表示任务执行的间隔周期,若为0则表示不是周期性执行任务

*/

private final long period;

/*省略以下代码*/

}

DelayedWorkQueue

DelayedWorkQueue 是一个优先队列,在元素出队时,ScheduledFutureTask 的 time 最小的元素将优先出队,如果 time 值相同则判断 sequenceNumber,先入队的元素先出队。

而 DelayedWorkQueue 也是 ScheduledExecutorService 能够定时执行任务的核心类。

首先回顾一下线程池的执行流程:

向线程池提交任务,这时任务将入队到该线程池的阻塞队列

工作线程不断从队列中取出任务,并执行,若然队列中没有任务,工作线程将阻塞直到任务的到来。

当工作线程执行 DelayedWorkQueue 的出队方法时,DelayedWorkQueue 首先获取到 time 值最小的 ScheduledFutureTask,即将要最先执行的任务。然后用 time 值(任务要执行的时间戳)与当前时间作比较,判断任务执行时间是否到期,若然到期,元素立马出队,交由工作线程执行。

但是当 time 值还没到期呢?那么 time 将会减去当前时间,得到 delay 值(延迟多少时间后执行任务),然后使用方法Condition.awaitNanos(long nanosTimeout),阻塞获取任务的工作线程,直到经过了 delay 时间,即到达了任务的执行时间,元素才会出队,交由工作线程执行。

scheduleAtFixedRate 与 scheduleWithFixedDelay

根据我之前的理解,认为 scheduleAtFixedRate 是绝对周期性执行,例如间隔周期为 10 秒,那么任务每隔 10 秒都会执行一次,不管任务是否成功执行。但是我的理解是错误的,这两个方法的功能分别是:

scheduleAtFixedRate:任务执行完成后,在提交任务到任务执行完成后的时间是否经过了 period,若然经过了,即马上再次执行该任务。否则等待,直到提交任务到现在已经经过了 period 时间,再次执行该任务。

scheduleWithFixedDelay:任务执行完成后,等待 delay 时间,然后再次执行。

要清楚,一个定时任务,不管是否为周期性执行,都将会只由一条工作线程执行

首先看下这两个方法的源码

public ScheduledFuture> scheduleAtFixedRate(Runnable command,

long initialDelay,

long period,

TimeUnit unit) {

if (command == null || unit == null)

throw new NullPointerException();

if (period <= 0)

throw new IllegalArgumentException();

ScheduledFutureTask sft =

new ScheduledFutureTask(command,

null,

triggerTime(initialDelay, unit),

unit.toNanos(period));

RunnableScheduledFuture t = decorateTask(command, sft);

sft.outerTask = t;

delayedExecute(t);

return t;

}

public ScheduledFuture> scheduleWithFixedDelay(Runnable command,

long initialDelay,

long delay,

TimeUnit unit) {

if (command == null || unit == null)

throw new NullPointerException();

if (delay <= 0)

throw new IllegalArgumentException();

ScheduledFutureTask sft =

new ScheduledFutureTask(command,

null,

triggerTime(initialDelay, unit),

unit.toNanos(-delay));

RunnableScheduledFuture t = decorateTask(command, sft);

sft.outerTask = t;

delayedExecute(t);

return t;

}

其实两个方法没有太大区别,只是在构建 ScheduledFutureTask 的时候,ScheduledFutureTask 的 period 属性有正负差别,scheduleAtFixedRate 方法构建 ScheduledFutureTask 的 period 为负数,而 scheduleWithFixedDelay 为正数。

接下来查看 ScheduledFutureTask 的 run 方法,工作线程在执行任务时将会调用该方法

/**

* Overrides FutureTask version so as to reset/requeue if periodic.

*/

public void run() {

boolean periodic = isPeriodic();

if (!canRunInCurrentRunState(periodic))

cancel(false);//1

else if (!periodic)

ScheduledFutureTask.super.run();//2

else if (ScheduledFutureTask.super.runAndReset()) {

//3

setNextRunTime();

reExecutePeriodic(outerTask);

}

}

如果定时任务时周期性执行方法,将会进入到 3 的执行逻辑,当然在这之前将会调用 runAndReset 执行任务逻辑。

当任务逻辑执行完成后,将会调用 setNextRunTime。

/**

* Sets the next time to run for a periodic task.

*/

private void setNextRunTime() {

long p = period;

if (p > 0)

time += p;

else

time = triggerTime(-p);

}

long triggerTime(long delay) {

return now() +

((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));

}

如果 period 为正数数,即执行的方法为 scheduleAtFixedRate,在任务的执行时间上添加 period 时间。

而 period 为负数,即执行的方法为 scheduleWithFixedDelay,将 time 改写为当前时间加上 period 时间。

执行完 setNextRunTime 方法后,将执行 reExecutePeriodic 方法,即重新将该 ScheduledFutureTask 对象,重新添加到队列中,等待下一次执行。

要清楚,不论调用哪个周期性执行方法,都是需要等到任务逻辑执行完成后,才能再次添加到队列中,等待下一次执行。

scheduleAtFixedRate 方法,每次都是在 time 的基础上添加 period 时间,如果任务逻辑的执行时间大于 period,那么在定时任务再次出队前,time 必定是小于当前时间,马上出队被工作线程执行。因为 time 每次都是任务开始执行的时间点。

scheduleWithFixedDelay 方法,每次都将 time 设置为当前时间加上 period,那么轮到定时任务再次出队时,必定是经过了 period 时间,才能被工作线程执行。

总结

对于 ScheduledExecutorService 一定要清楚,周期性执行任务,一定是等到上一次执行完成后,才能再次执行,即每个任务只由一条线程执行。那么要实现当达到一定时候后,不论任务是否执行完成,都将再次执行任务的功能,ScheduledExecutorService 的两个周期性执行方法都是不能实现的。其实也就是对于复杂的时间调度控制,ScheduledExecutorService 并不在行。

java time 周期性执行,详解ScheduledExecutorService的周期性执行方法相关推荐

  1. 详解ScheduledExecutorService的周期性执行方法

    2019独角兽企业重金招聘Python工程师标准>>> 详解 ScheduledExecutorService 的周期性执行方法 在最近的工作中,需要实现一个当一个任务执行完后,再等 ...

  2. java setpaintmode 用法_详解Paint的各种set方法

    (1) 在application配置文件中关闭硬件加速: android:allowBackup="true"android:hardwareAccelerated="f ...

  3. JAVA 多线程并发超详解

    JAVA 多线程并发超详解(未完,下一篇文章还有) 1. JAVA 多线程并发 1.1.1. JAVA 并发知识库 1.1.2. JAVA 线程实现/创建方式 1.1.2.1. 继承 Thread 类 ...

  4. Java线程池ThreadPool详解

    Java线程池ThreadPool详解 1. 线程池概述 1.1 线程池简介 1.2 线程池特点 1.3 线程池解决问题 2. 线程池原理分析 2.1 线程池总体设计 2.6 线程池流转状态 2.2 ...

  5. Java单元测试之JUnit4详解

    2019独角兽企业重金招聘Python工程师标准>>> Java单元测试之JUnit4详解 与JUnit3不同,JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @B ...

  6. java -jar 和 -cp详解

    java -jar 和 -cp详解 命令行执行程序 假如我们有一个程序,把它打包成Test.jar,如何运行才能成功输出Hello World package com.test; public cla ...

  7. 你真的弄明白了吗?Java并发之AQS详解

    你真的弄明白了吗?Java并发之AQS详解 带着问题阅读 1.什么是AQS,它有什么作用,核心思想是什么 2.AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁 3.AQS在J ...

  8. java定时任务框架elasticjob详解

    这篇文章主要介绍了java定时任务框架elasticjob详解,Elastic-Job是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架.该项目基于成熟的开源产品Quartz和Zo ...

  9. Java中JDBC连接数据库详解

    今天动力节点java学院小编分享的是JDBC连接数据库的相关知识,希望通过看过此文,各位小伙伴对DBC连接数据库有所了解,下面就跟随小编一起来看看JDBC连接数据库的知识吧. 一.JDBC连接数据库概 ...

最新文章

  1. nginx不同server不同日志文件_招标里的答疑是什么?和澄清文件有何不同?
  2. UITextView实现PlaceHolder的方式
  3. Mybatis获得参数值的两种方式:#{}和${}的区别
  4. 转: 如何从keystore file中查看数字证书信息
  5. java 监听队列_spring+activemq实战之配置监听多队列实现不同队列消息消费
  6. python3精要(84)-字节码原理及分析(2)
  7. HUAWEI nova 青春版闪速快充,让追剧不再断电
  8. 显示控制器注释_欧姆龙可编程控制器CS1D-CPU产品型号说明及功能介绍
  9. 2013计算机一级考试综合试题答案,2013全国计算机等级考试试题题库及答案.doc
  10. SimpleXML操控XML
  11. 双击ctrl搜索 意在颠覆用户的习惯
  12. 11.云计算平台(数据科学概论)
  13. vector的初始化和使用
  14. 供应链金融如何促进产业融合?
  15. 非平衡电桥电阻计算_非平衡电桥的原理和应用 - 范文中心
  16. 天津麒麟收购中标软件,国产操作系统新旗舰扬帆起航!
  17. 凭借这份《2022测试面经》候选者逆袭面试官,offer拿到手软
  18. 谈谈 DNS 原理及“域名劫持”和“域名欺骗/域名污染”
  19. 可爱的小老鼠计算机教案,大班语言儿歌教案《小老鼠玩电脑》
  20. vue使用video和vue-video-player并且可实现视频铺满呦

热门文章

  1. C盘过满或者重装系统小技巧(不需要重做系统)
  2. AlertDialog(对话框)的基本使用
  3. CCF 2019年题目题解 - Python
  4. C++:读写二进制文件到double数组
  5. tensorflow 进阶(二),BP神经网络
  6. pandas 笔记:聚合函数agg
  7. Java高阶部分知识点汇总(二)-封装与隐藏详讲
  8. python的可变长参数
  9. numpy.triu详解
  10. APACHE TOMCAT INTERVIEW QUESTIONS ANSWERS【转】