scheduleAtFixedRate和scheduleWithFixedDelay探究

scheduleAtFixedRate是用任务开始时间计算间隔,就是说某任务上次理论启动时间+间隔时间就是下次启动时间。
scheduleWithFixedDelay是用任务结束时间计算间隔,就是说某任务上次结束时间+间隔时间就是下次启动时间。

这段代码模拟了一组10个任务,每个任务都有个name(任务名)和time(任务花费的时间)。第6个任务(任务名为任务5)耗时15s,其他任务耗时1s。这一组任务分别用scheduleAtFixedRate(固定频率)和scheduleWithFixedDelay(固定延迟)处理,截取部分日志解析这两个方法的运行逻辑。


import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

@Slf4j
public class MyConcurrentTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ScheduledExecutorService readThreadPool = Executors.newScheduledThreadPool(5);
        List<ScheduledFuture> futureList = new ArrayList<>();
        //用objects模拟任务列表,任务的time代表耗费的时间
        List<Map<String,Object>> objects = new ArrayList<>();
        for(int i = 0 ; i < 10 ; i++){
            Map<String,Object> object = new HashMap<>();
            object.put("name","任务"+i);
            object.put("time",1000);
            objects.add(object);
        }
        //第6个任务sleep15s,第6个任务是任务5
        objects.get(5).put("time",15000);
        System.out.println(objects);
        for(Map<String,Object> object : objects){
            //任务在0s后开始,任务间隔是5s
            ScheduledFuture future = readThreadPool.scheduleAtFixedRate(()->{
                log.info(object.toString());
                try {
                    //暂停指定时间
                    Thread.sleep((int)object.get("time"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },0,5, TimeUnit.SECONDS);
            futureList.add(future);
        }
        for(ScheduledFuture future:futureList){
            future.get();
        }
    }
}

用scheduleAtFixedRate(固定频率)处理的日志。

--10个任务的详情
[{name=任务0, time=1000}, {name=任务1, time=1000}, {name=任务2, time=1000}, {name=任务3, time=1000}, {name=任务4, time=1000}, {name=任务5, time=15000}, {name=任务6, time=1000}, {name=任务7, time=1000}, {name=任务8, time=1000}, {name=任务9, time=1000}]
--线程池有5个核心线程,同时开始了5个任务。
11:06:06.505 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务4, time=1000}
11:06:06.505 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=1000}
11:06:06.505 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务1, time=1000}
11:06:06.505 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务3, time=1000}
11:06:06.505 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务2, time=1000}
--1s之后任务处理完,处理其他的任务。
11:06:07.505 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务5, time=15000}
11:06:07.505 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务9, time=1000}
11:06:07.505 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务6, time=1000}
11:06:07.505 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务7, time=1000}
11:06:07.505 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务8, time=1000}
--4s之后距离任务0/1/2/3/4上次启动已经过去了5s,任务0/1/2/3/4都已经准备好第二次执行,但此时只有四个空闲线程。按照顺序任务4没有执行。
11:06:11.505 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务0, time=1000}
11:06:11.505 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务1, time=1000}
11:06:11.505 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务2, time=1000}
11:06:11.505 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务3, time=1000}
--1s之后除了任务4,任务5/6/7/8/9也都准备好第二次执行,但此时任务5第一次执行还没有结束,所以任务5第二次执行被阻塞。按照顺序,任务9没有执行。
11:06:12.506 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务6, time=1000}
11:06:12.506 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务8, time=1000}
11:06:12.506 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务4, time=1000}
11:06:12.506 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务7, time=1000}
--1s之后任务5/9准备好执行,此时任务5第二次执行依然被阻塞。
11:06:13.521 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务9, time=1000}
--3s之后任务0/1/2/3准备好第三次执行……
11:06:16.506 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=1000}
11:06:16.506 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务2, time=1000}
11:06:16.506 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务1, time=1000}
11:06:16.506 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务3, time=1000}
--1s之后……
11:06:17.506 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务4, time=1000}
11:06:17.506 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务7, time=1000}
11:06:17.506 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务8, time=1000}
11:06:17.506 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务6, time=1000}
--1s之后……
11:06:18.506 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务9, time=1000}
--3s之后……
11:06:21.507 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务1, time=1000}
11:06:21.507 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务3, time=1000}
11:06:21.507 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=1000}
11:06:21.507 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务2, time=1000}
--1s之后,任务5第二次执行,因为任务5第一次执行开始时间到现在时间间隔大于规定的时间间隔,即任务5第二次执行已经“迟到”,应立即执行。
11:06:22.507 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务5, time=15000}
11:06:22.522 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务7, time=1000}
11:06:22.522 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务8, time=1000}
11:06:22.522 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务4, time=1000}
11:06:22.522 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务6, time=1000}
11:06:23.523 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务9, time=1000}

综上所述,scheduleAtFixedRate(固定频率)是在任务上次开始时间+间隔时间开始的,拿任务0来说,第一次启动是在11:06:06,第二次启动是在11:06:11,中间间隔了5s,这正是我们规定的间隔时间,以此来达到频率固定的效果(当然频率也不是绝对固定的,如任务4)。

java.util.concurrent.ScheduledThreadPoolExecutor.ScheduledFutureTask#run源码得知,只有在任务从队列中取出并执行时才会往任务队列中放入下次任务。下次任务的启动时间是根据本次任务的理论启动时间计算的(任务队列中的每个任务都会有一个理论启动时间,但是可能会因为没有抢到线程而延迟,所以任务的实际启动时间不一定是理论启动时间)。所以,如果任务A执行时间过长,任务A'会一直等待(即使超时),当任务A'执行的时候可能会把任务A''的启动时间设置到任务A'实际启动时间之前。

/**
    * Overrides FutureTask version so as to reset/requeue if periodic.
    */
public void run() {
    boolean periodic = isPeriodic();
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    else if (!periodic)
        ScheduledFutureTask.super.run();
    else if (ScheduledFutureTask.super.runAndReset()) {
        /*在任务执行时,修改任务下次启动时间并重新放回队列。
         *scheduleAtFixedRate就会以此启动时间+间隔时间计算下次启动时间。
         */
        setNextRunTime();
        reExecutePeriodic(outerTask);
    }
}

注意:如果任务都是第一次处理很耗时间,之后耗时很短的话,会出现连续处理多次某个任务的情况。上面的代码,我们在lambda表达式最后一行添加object.put("time",0);,在处理完第一遍之后把所有任务的处理时间全都设成0s。这样当任务5执行完之后,任务5会立刻执行多次。如下日志:

12:48:14.490 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务4, time=1000}
12:48:14.490 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=1000}
12:48:14.490 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务2, time=1000}
12:48:14.490 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务1, time=1000}
12:48:14.490 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务3, time=1000}
12:48:15.490 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务7, time=1000}
12:48:15.490 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务9, time=1000}
12:48:15.490 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务6, time=1000}
12:48:15.490 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务8, time=1000}
12:48:15.490 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务5, time=15000}
12:48:19.507 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务0, time=0}
12:48:19.507 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务1, time=0}
12:48:19.507 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务2, time=0}
12:48:19.507 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务3, time=0}
12:48:19.507 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务4, time=0}
12:48:19.507 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务6, time=0}
12:48:19.507 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务7, time=0}
12:48:19.507 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务8, time=0}
12:48:19.507 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务9, time=0}
12:48:24.507 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务1, time=0}
12:48:24.507 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=0}
12:48:24.507 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务2, time=0}
12:48:24.507 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务3, time=0}
12:48:24.507 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务4, time=0}
12:48:24.507 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务6, time=0}
12:48:24.507 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务7, time=0}
12:48:24.507 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务8, time=0}
12:48:24.507 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务9, time=0}
12:48:29.509 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务1, time=0}
12:48:29.509 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务2, time=0}
12:48:29.509 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务3, time=0}
12:48:29.509 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务0, time=0}
12:48:29.509 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务4, time=0}
12:48:29.509 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务6, time=0}
12:48:29.509 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务7, time=0}
12:48:29.509 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务8, time=0}
12:48:29.509 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务9, time=0}
--连续执行了三遍任务5
12:48:30.493 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务5, time=0}
12:48:30.493 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务5, time=0}
12:48:30.493 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务5, time=0}

用scheduleWithFixedDelay(固定延迟)处理的日志。

--10个任务的详情
[{name=任务0, time=1000}, {name=任务1, time=1000}, {name=任务2, time=1000}, {name=任务3, time=1000}, {name=任务4, time=1000}, {name=任务5, time=15000}, {name=任务6, time=1000}, {name=任务7, time=1000}, {name=任务8, time=1000}, {name=任务9, time=1000}]
--线程池有5个核心线程,同时开始了5个任务。
11:51:51.450 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务4, time=1000}
11:51:51.450 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务1, time=1000}
11:51:51.450 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=1000}
11:51:51.450 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务2, time=1000}
11:51:51.450 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务3, time=1000}
--1s之后任务处理完,处理其他的任务。
11:51:52.466 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务7, time=1000}
11:51:52.466 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务9, time=1000}
11:51:52.466 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务6, time=1000}
11:51:52.466 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务8, time=1000}
11:51:52.466 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务5, time=15000}
--5s之后,距离任务0/1/2/3/4上次处理结束已经过去了5s,任务0/1/2/3/4都已经准备好第二次执行,但此时只有四个空闲线程。按照顺序任务4没有执行。
11:51:57.467 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务0, time=1000}
11:51:57.467 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务2, time=1000}
11:51:57.467 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务1, time=1000}
11:51:57.467 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务3, time=1000}
--1s之后除了任务4,任务6/7/8/9也都准备好第二次执行,任务5还没有结束第一次执行。
11:51:58.467 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务9, time=1000}
11:51:58.467 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务4, time=1000}
11:51:58.467 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务6, time=1000}
11:51:58.467 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务7, time=1000}
--1s之后执行任务8。
11:51:59.483 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务8, time=1000}
--4s之后任务0/1/2/3准备好第三次执行……
11:52:03.468 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务3, time=1000}
11:52:03.468 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务1, time=1000}
11:52:03.468 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务2, time=1000}
11:52:03.468 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=1000}
--1s之后……
11:52:04.484 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务7, time=1000}
11:52:04.484 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务6, time=1000}
11:52:04.484 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务4, time=1000}
11:52:04.484 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务9, time=1000}
--1s之后……
11:52:05.500 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务8, time=1000}
--4s之后……
11:52:09.469 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务0, time=1000}
11:52:09.469 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务1, time=1000}
11:52:09.469 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务3, time=1000}
11:52:09.469 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务2, time=1000}
--1s之后……
11:52:10.501 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务4, time=1000}
11:52:10.501 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务7, time=1000}
11:52:10.501 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务6, time=1000}
11:52:10.501 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务9, time=1000}
--1s之后……
11:52:11.501 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务8, time=1000}
--1s之后距离任务5处理(11:51:52--11:52:07)结束已经过去了5s,任务5第二次执行。
11:52:12.471 [pool-1-thread-1] INFO MyConcurrentTest - {name=任务5, time=15000}
11:52:15.471 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务1, time=1000}
11:52:15.471 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务0, time=1000}
11:52:15.471 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务3, time=1000}
11:52:15.471 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务2, time=1000}
11:52:16.518 [pool-1-thread-4] INFO MyConcurrentTest - {name=任务4, time=1000}
11:52:16.518 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务7, time=1000}
11:52:16.518 [pool-1-thread-2] INFO MyConcurrentTest - {name=任务9, time=1000}
11:52:16.518 [pool-1-thread-3] INFO MyConcurrentTest - {name=任务6, time=1000}
11:52:17.534 [pool-1-thread-5] INFO MyConcurrentTest - {name=任务8, time=1000}

综上所述,scheduleWithFixedDelay(固定延迟)是在任务上次结束时间+间隔时间开始的,任务如果没有结束,任务队列是没有下次执行计划的。

posted @ 2020-02-05 15:50  嘘,别吵  阅读(1114)  评论(0编辑  收藏  举报