SpringBoot定时任务 - ScheduleExecutorService实现方式

上文介绍的Timer在实际开发中很少被使用, 因为Timer底层是使用一个单线来实现多个Timer任务处理的,所有任务都是由同一个线程来调度,所有任务都是串行执行。而ScheduledExecutorService是基于线程池的,可以开启多个线程进行执行多个任务,每个任务开启一个线程; 这样任务的延迟和未处理异常就不会影响其它任务的执行了。@pdai

知识准备

需要对ScheduledExecutorService 代替 Timer的原因以及ScheduledExecutorService所在的知识体系有了解。

为什么用ScheduledExecutorService 代替 Timer?

上文我们说到Timer底层是使用一个单线来实现多个Timer任务处理的,所有任务都是由同一个线程来调度,所有任务都是串行执行,意味着同一时间只能有一个任务得到执行,而前一个任务的延迟或者异常会影响到之后的任务。

如果有一个定时任务在运行时,产生未处理的异常,那么当前这个线程就会停止,那么所有的定时任务都会停止,受到影响。

而ScheduledExecutorService是基于线程池的,可以开启多个线程进行执行多个任务,每个任务开启一个线程; 这样任务的延迟和未处理异常就不会影响其它任务的执行了。

ScheduledExecutorService所在的线程池的知识体系?

属于Java并发中JUC,具体可以看JUC - 类汇总和学习指南

ScheduledExecutorService实现案例

ScheduledExecutorService使用例子如下。

schedule

执行定时任务,延迟1秒开始执行。

@SneakyThrows
public static void timer() {
    // start timer
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        public void run() {
            log.info("timer-task @{}", LocalDateTime.now());
        }
    }, 1000);

    // waiting to process(sleep to mock)
    Thread.sleep(3000);

    // stop timer
    timer.cancel();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

输出

10:05:47.440 [Timer-0] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-task @2021-10-01T20:05:47.436
1

scheduleAtFixedRate

延迟0.5秒开始执行,每秒执行一次, 10秒后停止。

同时测试某次任务执行时间大于周期时间的变化。

@SneakyThrows
public static void timerFixedRate() {
    // start timer
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        int count = 0;

        @SneakyThrows
        public void run() {
            if (count++==2) {
                Thread.sleep(5000); // 某一次执行时间超过了period(执行周期)
            }
            log.info("timer-fixedRate-task @{}", LocalDateTime.now());

        }
    }, 500, 1000);

    // waiting to process(sleep to mock)
    Thread.sleep(10000);

    // stop timer
    timer.cancel();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

输出

10:05:59.781 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:05:59.781
10:06:00.782 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:00.782
10:06:06.783 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:06.783
10:06:06.783 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:06.783
10:06:06.783 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:06.783
10:06:06.783 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:06.783
10:06:06.783 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:06.783
10:06:06.783 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:06.783
10:06:07.781 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:07.781
10:06:08.781 [Timer-2] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-fixedRate-task @2021-10-01T10:06:08.781
1
2
3
4
5
6
7
8
9
10

(你会发现周期执行1秒中执行一次,但是某次执行了5秒,这时候,后续的任务会加快执行进度,一次性就执行了,执行的时间都是10:06:06.783, 所以scheduleAtFixedRate最大的特点是保证了总时间段内的执行次数

scheduleWithFixedDelay

延迟0.5秒开始执行,每秒执行一次, 10秒后停止。

@SneakyThrows
public static void timerPeriod() {
    // start timer
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @SneakyThrows
        public void run() {
            log.info("timer-period-task @{}", LocalDateTime.now());
            Thread.sleep(100); // 可以设置的执行时间, 来测试当执行时间大于执行周期时任务执行的变化 
        }
    }, 500, 1000);

    // waiting to process(sleep to mock)
    Thread.sleep(10000);

    // stop timer
    timer.cancel();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

输出

10:05:49.781 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:49.781
10:05:50.781 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:50.781
10:05:51.781 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:51.781
10:05:52.781 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:52.781
10:05:53.782 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:53.782
10:05:54.783 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:54.783
10:05:55.783 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:55.783
10:05:56.784 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:56.784
10:05:57.785 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:57.785
10:05:58.786 [Timer-1] INFO tech.pdai.springboot.schedule.timer.timertest.TimerTester - timer-period-task @2021-10-01T10:05:58.786
1
2
3
4
5
6
7
8
9
10

进一步理解

我们再通过一些问题来帮助你更深入理解ScheduleExecutorService实现方式。@pdai

schedule 和 scheduleAtFixedRate和 scheduleWithFixedDelay有何区别?

  • schedule:每次执行完当前任务后,然后间隔一个period的时间再执行下一个任务; 当某个任务执行周期大于时间间隔时,依然按照间隔时间执行下个任务,即它保证了任务之间执行的间隔

  • scheduleAtFixedRate:每次执行时间为上一次任务开始起向后推一个period间隔,也就是说下次执行时间相对于上一次任务开始的时间点;按照上述的例子,它保证了总时间段内的任务的执行次数

示例源码

https://github.dev/realpdai/tech-pdai-spring-demos

联系我

添加@pdai微信

PS:添加时请备注Java全栈,谢谢!