ScheduledExecutorService中scheduleAtFixedRate方法与scheduleWithFixedDelay方法的区别

  1. ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,可以作为线程池来使用,同时实现了ScheduledExecutorService接口,来执行一些周期性的任务。ScheduledExecutorService一般常用的方法主要就4个

        public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit);

    两个schedule方法都很明确,就是执行一次Runnable、Callable任务。

    scheduleAtFixedRate和scheduleWithFixedDelay这两个方法看起来就不是那么好区分了,今天就带大家从源码角度看看这两个方法的区别.

  2. 我们先看看这两个方法的区别

    下面是这两个方法的源码

​ 从上面的图上可以看到唯一的不同就是在创建ScheduledFutureTask对象的时候,scheduleWithFixedDelay将我们传入的delay取了个负数。所以这两个方法的区别都会在ScheduledFutureTask这个类中。

​ 先说下ScheduledFutureTask这个类吧,它是ScheduledThreadPoolExecutor的内部类,我们看下它的继承关系

从上图中能看到ScheduledFutureTask,间接继承了Runnable接口,会实现run方法。而我们ScheduledThreadPoolExecutor类中真正执行任务的类其实也就是调用ScheduledFutureTask的run方法。也间接实现了Comparable接口的比较方法。

  1. 下面以scheduleAtFixedRate看看内部调用逻辑

        public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
    long initialDelay,
    long period,
    TimeUnit unit) {
    if (command == null || unit == null)
    throw new NullPointerException();
    if (period <= 0L)
    throw new IllegalArgumentException();
    ScheduledFutureTask<Void> sft =
    new ScheduledFutureTask<Void>(command,
    null,
    //这里是计算首次执行实现
    triggerTime(initialDelay, unit),
    unit.toNanos(period),
    sequencer.getAndIncrement());
    //这里当前是直接返回的上面的sft,这个是留给子类去扩展的
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    //所以这里也就是把上面创建的ScheduledFutureTask加到线程池的任务队列中去
    delayedExecute(t);
    return t;
    }

    这里是ScheduledFutureTask的构造方法

            ScheduledFutureTask(Runnable r, V result, long triggerTime,
    long period, long sequenceNumber) {
    super(r, result);
    //这个是任务首次执行的时间
    this.time = triggerTime;
    //这里的代码很简单,只是将它赋值给了成员变量period。
    //其中scheduleWithFixedDelay是在外面取了个负数传了进来,scheduleAtFixedRate则是原样传了进来
    this.period = period;
    //这个是AtomicLong类型的,每次都+1,对我们加入的任务做了个编号
    this.sequenceNumber = sequenceNumber;
    }

    我们再看看delayedExecute(t);的内部

        private void delayedExecute(RunnableScheduledFuture<?> task) {
    if (isShutdown())
    reject(task);
    else {
    //重点在这里,这里会把上面的ScheduledFutureTask加入到线程池的任务队列中,
    //这里的super.getQueue()这个队列,是在ScheduledThreadPoolExecutor构造方法中定义的,也是ScheduledThreadPoolExecutord的内部类,类名是DelayedWorkQueue
    //DelayedWorkQueue其实是一个最小堆,会对加入它的元素,调用compareTo方法进行排序,首个元素是最小的
    //对应当前这里,就是调用ScheduledFutureTask的compareTo方法进行排序,也就是队列中的任务是按照执行时间的先后顺序排序的
    //最终线程池执行任务的时候从首部依次获取task,具体获取任务的时候,DelayedWorkQueue会首先获取任务,查看对应的执行时间,如果任务时间没有到,就会调用Condition.awaitNanos去暂停,直到到达执行时间或者通过给队列中添加任务调用Condition.signal去唤醒
    super.getQueue().add(task);
    if (!canRunInCurrentRunState(task) && remove(task))
    task.cancel(false);
    else
    //这里是根据线程池当前的线程数,如果小于核心线程数,就会新启动线程去执行任务
    ensurePrestart();
    }
    }

    上面已经可以看到把任务已经加到线程池中去了,后面就是具体由线程池去执行任务了,所以我们直接去ScheduledFutureTask查看run方法就可以了

            public void run() {
    if (!canRunInCurrentRunState(this))
    cancel(false);
    else if (!isPeriodic())
    super.run();
    //具体在这里会调用我们传入的run方法
    else if (super.runAndReset()) {
    //在这里会更新成员变量time,scheduleAtFixedRate和scheduleWithFixedDelay的区别也全在这里了,下面我们去看看这里
    setNextRunTime();
    //这里会重新将outerTask加入到线程池的任务队列中,这里的outerTask==我们当前执行run方法的对象this
    reExecutePeriodic(outerTask);
    }
    }
    }

    通过上面的代码也能看到,我们的run方法是不会同时由多次执行的,举个例子,如果我们调用scheduleAtFixedRate或者scheduleWithFixedDelay方法,传入的Runnable的对象,需要执行10s,而我们设定的周期是2s,是不会在第一次Runnable的10s的周期任务启动后2s,就启动第2次周期任务的。它只会在第一个Runnable的10s的周期任务结束后,重新加入到任务队列中之后,才会启动下次的任务。

        private void setNextRunTime() {
    //这里的period ,scheduleAtFixedRate传入的是正数,scheduleWithFixedDelay传入的是负数
    long p = period;
    if (p > 0)
    //所以scheduleAtFixedRate会走这里,这里的time开始时时首次任务的开始执行时间,所以下次任务的时间就是(开始添加任务时计算出来的首次任务执行时间(这个时间不一定是任务首次执行的真正时间)+(任务执行次数-1)*period)
    time += p;
    else
    //这里对p取负,就会还原成正数,也就是我们最初调用scheduleWithFixedDelay时传入的值,这里的下次执行时间会用当前系统时间(可以看成当前Runnable执行的结束时间)+period来设置
    time = triggerTime(-p);
    }
  2. 结论

    scheduleAtFixedRate或者scheduleWithFixedDelay对于从第2次开始的任务的计算时间不一样:

    • scheduleAtFixedRate 下次任务的时间=(开始添加任务时计算出来的首次任务执行时间+(任务执行次数-1)*period
    • scheduleWithFixedDelay 下次任务的时间=当前任务结束时间+period

    需要注意的是,下次任务时间都只是计算出来的理论值,如果任务的执行时间大于周期任务的period,或者设置的线程池中线程太少,就会出现下次任务执行时间<时间任务执行时间

ScheduledExecutorService中scheduleAtFixedRate方法与scheduleWithFixedDelay方法的区别的更多相关文章

  1. 理解ScheduledExecutorService中scheduleAtFixedRate和scheduleWithFixedDelay的区别

    scheduleAtFixedRate 每间隔一段时间执行,分为两种情况: 当前任务执行时间小于间隔时间,每次到点即执行: /** * 任务执行时间(8s)小于间隔时间(10s) */ public ...

  2. 关于scheduleAtFixedRate方法与scheduleWithFixedDelay的使用

    一.scheduleAtFixedRate方法 该方法是ScheduledExecutorService中的方法,用来实现周期性执行给定的任务,public ScheduledFuture<?& ...

  3. 聊Java中的任务调度的实现方法及比较

    前言 任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务.本文由浅入深介绍四种任务调度的 Java 实现: Timer ScheduledExecutor 开源工具包 Quartz ...

  4. 【转】Android开发中让你省时省力的方法、类、接口

    转载 http://www.toutiao.com/i6362292864885457410/?tt_from=mobile_qq&utm_campaign=client_share& ...

  5. J2EE项目开发中常用到的公共方法

    在项目IDCM中涉及到多种工单,包括有:服务器|网络设备上下架工单.服务器|网络设备重启工单.服务器光纤网线更换工单.网络设备撤线布线工单.服务器|网络设备替换工单.服务器|网络设备RMA工单.通用原 ...

  6. (转)ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务 的解决方法

    早上同事用PL/SQL连接虚拟机中的Oracle数据库,发现又报了"ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务"错误,帮其解决后,发现很多人遇到过这样的问 ...

  7. 解析Jquery取得iframe中元素的几种方法

    iframe在复合文档中经常用到,利用jquery操作iframe可以大幅提高效率,这里收集一些基本操作,需要的朋友可以参考下   DOM方法:父窗口操作IFRAME:window.frames[&q ...

  8. 在html中添加script脚本的方法和注意事项

    在html中添加script脚本有两种方法,直接将javascript代码添加到html中与添加外部js文件,这两种方法都比较常用,大家可以根据自己需要自由选择 在html中添加<script& ...

  9. MVC中使用Entity Framework 基于方法的查询学习笔记 (二)

    解释,不解释: 紧接上文,我们在Visual Studio2012中看到系统为我们自动创建的视图(View)文件Index.cshtml中,开头有如下这句话: @model IEnumerable&l ...

随机推荐

  1. Unittest方法 -- 测试固件(TestFixture)

    前置和后置 1.setUp:在写测试用例的时候,每次操作其实都是基于打开浏览器输入对应网址这些操作,这个就是执行用例的前置条件.2.tearDown:执行完用例后,为了不影响下一次用例的执行,一般有个 ...

  2. NestJS WebSocket 开始使用

    使用NestJs提供WebSocket服务. 本文会在新建项目的基础上增加2个类 Gateway 实现业务逻辑的地方 WebSocketAdapter WebSocket适配器 新建项目 新建一个项目 ...

  3. Linux day2 随堂笔记

    计算机的硬件组成 主机.输入设备.输出设备 一.运维人员的核心职责 1. 企业数据安全 2. 企业业务724运行(不宕机) 3. 企业业务服务率高(用户体验好) 4. 运维人员的工作内容 日常服务器维 ...

  4. 解析ArrayList的底层实现(上)

    private static final long serialVersionUID = 8683452581122892189L;//唯一序列号ID private static final int ...

  5. npm 安装、卸载模块

    npm安装模块 [npm install xxx]利用 npm 安装xxx模块到当前命令行所在目录:[npm install -g xxx]利用npm安装全局模块xxx:本地安装时将模块写入packa ...

  6. js学习笔记之this指向及形参实参

    var length = 10 function fn () { console.log(this.length) } var obj = { length: 5, method (fn) { fn( ...

  7. Apache OfBiz 反序列化命令执行漏洞(CVE-2020-9496)

    影响版本 - Apache Ofbiz:< 17.12.04 访问 https://192.168.49.2:8443/webtools/control/xmlrpc 抓包 进行数据包修改 pa ...

  8. AT4828 [ABC152D] Handstand 2 TJ

    前言 洛谷题解,懂?( 题目链接 来一点不一样的方法. 正解:动态规划 / 打表数据暴力分析 考试半小时想出方法,最后输在了两个细节上. 写一篇题解以此纪念. 打表暴力程序 最开始打的暴力对拍,没想到 ...

  9. LAMP介绍以及Apache安装

    一.LAMP架构介绍 1.1 LAMP概述 LAMP架构是目前成熟的企业网站应用模式之一,指的是协同工作的一整套系统和相关软件,能够提供动态Web站点服务及其应用开发环境.LAMP是一个缩写词,具体包 ...

  10. 虚拟机中桥接模式和NAT模式以及仅主机模式的区别

    桥接模式和NAT模式 网络连接类型的选择,网络连接类型一共有桥接.NAT.仅主机和不联网四种. 桥接:选择桥接模式的话虚拟机和宿主机在网络上就是平级的关系,相当于连接在同一交换机上. NAT:NAT模 ...