一文带你搞懂 ScheduledExecutorService 定时任务类(二)

623 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

前言

上一篇文章 一文带你搞懂 ScheduledExecutorService 定时任务类(一) 我们主要看了下 ScheduledExecutorService 定时任务类的应用 demo 、两个主要方法 scheduleAtFixedRate 和 scheduleWithFixedDelay。本篇文章,我们一起来深入地分析 ScheduledExecutorService 的源码。

ScheduledThreadPoolExecutor 类

首先,ScheduledExecutorService 是一个接口,它的具体实现类是 ScheduledThreadPoolExecutor,从源码中可以知道,ScheduledThreadPoolExecutor 继承了 ThreadPoolExecutor 类,同时实现了 ScheduledExecutorService 接口。因此,ScheduledThreadPoolExecutor 既是定时器,又是线程池

image.png

scheduleAtFixedRate 方法

首先,我们看一下 scheduleAtFixedRate 的实现:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
    if (command != null && unit != null) {
        if (period <= 0L) {
            throw new IllegalArgumentException();
        } else {
            ScheduledThreadPoolExecutor.ScheduledFutureTask<Void> sft = new ScheduledThreadPoolExecutor.ScheduledFutureTask(command, (Object)null, this.triggerTime(initialDelay, unit), unit.toNanos(period), sequencer.getAndIncrement());
            RunnableScheduledFuture<Void> t = this.decorateTask((Runnable)command, sft);
            sft.outerTask = t;
            this.delayedExecute(t);
            return t;
        }
    } else {
        throw new NullPointerException();
    }
}

该方法主要有以下几步:

  1. 校验任务和时间单位是否为空,若其中一个为空,则抛出空指针异常
  2. 校验执行周期 period 是否小于 1 ,如果是则抛出不合法参数异常
  3. 将任务、触发时间、执行周期、创建 ScheduledFutureTask 类,sequencer 计数器加一(并发安全的),sequencer 的作用是:如果两个任务执行时间相同,就可以根据该变量的大小来判断哪个任务先执行
  4. 将任务和 ScheduledFutureTask 组装成 RunnableScheduledFuture,执行decorateTask方法,主要作用是一个扩展点,允许用户修改和替换task
  5. 执行 delayedExecute 方法,它的作用是通过把任务放到一个 BlockingQueue 队列中,并且通过ensurePrestart 方法去保证至少有一个线程开始执行,ensurePrestart 是线程池的方法,它保证有线程能启动去执行任务,有关线程池的原理,在这里就不过多赘述

上述五个步骤,就将一个任务放入到线程池中了,具体要如何周期性运行,是通过 run 方法来执行的:

image.png

代码有三个分支:

  1. 首先是判断当前任务是否需要执行,如果不需要,则取消
  2. 如果是一次性任务,就执行 ScheduledFutureTask.super.run() 这个方法,即执行我们重新的Runnable的任务
  3. 如果是周期任务,就执行 ScheduledFutureTask.super.runAndReset() ,该方法执行逻辑为:获取下次执行的时间,然后再将任务重新加入到 BlockingQueue 队列,最后调用任务

总结

本文主要讲解了 ScheduledExecutorService 实现定时任务线程池的源码,核心其实主要在于线程池 + 任务队列 + 计算下一次执行时间的组合应用。感谢大家的支持~