彻底搞懂ScheduledThreadPoolExecutor

本文主要分享支持周期性任务调度的线程池ScheduledThreadPoolExecutor ,而对于ScheduledThreadPoolExecutor的应用也可以看看我们在前面一段时间写过的一遍关于SpringBoot动态定时任务的文章玩转SpringBoot动态定时任务(启动、暂停)

介绍

ScheduledThreadPoolExecutor 继承ThreadPoolExecutor来重用线程池的功能

ScheduledThreadPoolExecutor 内部构造了两个内部类 ScheduledFutureTaskDelayedWorkQueue:

  • ScheduledFutureTask: ScheduledThreadPoolExecutor使用专门的任务类型ScheduledFutureTask 来执行周期任务,它继承了FutureTask,还分别实现了Runnable、Future、Delayed接口,说明它是一个可以延迟执行的异步运算任务。

  • DelayedWorkQueue: 这是 ScheduledThreadPoolExecutor 为存储周期或延迟任务专门定义的一个延迟队列。它内部只允许存储 RunnableScheduledFuture 类型的任务。

源码解析

ScheduledFutureTask内部类

构造函数
java 复制代码
private class ScheduledFutureTask<V>
        extends FutureTask<V> implements RunnableScheduledFuture<V> {
 
    /** 顺序编号,当两个任务有相同的延迟时间时,按照 FIFO 的顺序入队*/
    private final long sequenceNumber;

    /** 下次任务执行时的时间 */
    private long time;

    /** 执行周期时间*/
    private final long period;

    /** 重新入队的任务*/
    RunnableScheduledFuture<V> outerTask = this;

    /** 延迟队列的索引 */
    int heapIndex;

    ScheduledFutureTask(Runnable r, V result, long ns) {
        super(r, result);
        this.time = ns;
        this.period = 0;
        this.sequenceNumber = sequencer.getAndIncrement();
    }

    ScheduledFutureTask(Runnable r, V result, long ns, long period) {
        super(r, result);
        this.time = ns;
        this.period = period;
        this.sequenceNumber = sequencer.getAndIncrement();
    }

    ScheduledFutureTask(Callable<V> callable, long ns) {
        super(callable);
        this.time = ns;
        this.period = 0;
        this.sequenceNumber = sequencer.getAndIncrement();
    }

ScheduledFutureTask 继承自FutureTask,可以通过返回Future对象来获取执行的结果,将 Runnable/Callable 封装为 ScheduledFutureTask

run方法
java 复制代码
public void run() {
    // 是否为周期任务
    boolean periodic = isPeriodic();
    // 当前线程池运行状态下如果不可以执行任务,取消该任务
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    else if (!periodic)
        // 不是周期性任务,调用FutureTask中的run方法执行
        ScheduledFutureTask.super.run();
    // 周期任务    
    else if (ScheduledFutureTask.super.runAndReset()) {
        // 设置下次执行时间
        setNextRunTime();
        // 重新入队
        reExecutePeriodic(outerTask);
    }
}

setNextRunTime()方法 用来设置下一次运行的时间

java 复制代码
private void setNextRunTime() {
    long p = period;
    if (p > 0)
     // 固定频率,上次执行时间加上周期时间
        time += p;
    else
       // 固定延迟执行,使用当前系统时间加上周期时间
        time = triggerTime(-p);
}

period正数表示固定速率执行(为scheduleAtFixedRate提供服务),负数表示固定延迟执行(为scheduleWithFixedDelay提供服务),0表示不重复任务。

cancel方法
java 复制代码
public boolean cancel(boolean mayInterruptIfRunning) {
    // 调用父类 FutureTask.cancel 
    boolean cancelled = super.cancel(mayInterruptIfRunning);
    // 是否从队列中移除此任务
    if (cancelled && removeOnCancel && heapIndex >= 0)
        remove(this);
    return cancelled;
}

ScheduledThreadPoolExecutor线程池

构造函数
java 复制代码
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}

由于ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,ScheduledThreadPoolExecutor的构造方法都是直接调用其父类ThreadPoolExecutor类的构造方法。

Schedule延迟任务
java 复制代码
// 无返回值的延迟任务
public ScheduledFuture<?> schedule(Runnable command,
                                   long delay,
                                   TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    // decorateTask是一个钩子函数,子类利用它可以对任务进行重构过滤等操作
    RunnableScheduledFuture<?> t = decorateTask(command,new ScheduledFutureTask<Void>(command, null,triggerTime(delay, unit)));
    // 执行任务
    delayedExecute(t);
    return t;
}

// 有返回值的延迟任务
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                       long delay,
                                       TimeUnit unit) {
    if (callable == null || unit == null)
        throw new NullPointerException();
    // decorateTask是一个钩子函数,子类利用它可以对任务进行重构过滤等操作
    RunnableScheduledFuture<V> t = decorateTask(callable,new ScheduledFutureTask<V>(callable,triggerTime(delay, unit)));
    // 执行任务
    delayedExecute(t);
    return t;
}

schedule主要用于执行一次性(延迟)任务,可以看到这两个方法的入参一个是Runnable对象,一个Callable对象。传入的任务会封装成一个RunnableScheduledFuture对象,其实也就是ScheduledFutureTask对象。

scheduleAtFixedRate和scheduleWithFixedDelay周期任务
java 复制代码
// 创建一个周期执行的任务,下一次执行时间相当于是上一次的执行时间加上period
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<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period));
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;
}

// 创建一个周期执行的任务,下一次执行时间相当于是上一次任务执行完的系统时间加上period
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<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(-delay));
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;
}

可以看到这两个方法大体上没什么区别,主要区别在于 unit.toNanos方法,scheduleAtFixedRate传的是正值 ,而scheduleWithFixedDelay传的则是负值 ,这个值就是 ScheduledFutureTask 的period属性

我们上面介绍延迟任务和周期任务,最后都通过调用delayedExecute()方法来延时执行任务,返回一个ScheduledFuture对象。

java 复制代码
private void delayedExecute(RunnableScheduledFuture<?> task) {
    // 线程池已关闭,执行拒绝策略
    if (isShutdown())
        reject(task);
    else {
        // 任务入队
        super.getQueue().add(task);
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&
            remove(task))
            // 移除任务
            task.cancel(false);
        else
            // 启动一个新的线程等待任务
            ensurePrestart();
    }
}

ensurePrestart()方法

java 复制代码
void ensurePrestart() {
    int wc = workerCountOf(ctl.get());
    if (wc < corePoolSize)
        addWorker(null, true);
    else if (wc == 0)
        addWorker(null, false);
}

该方法在父类ThreadPoolExecutor中定义,调用了addWorker方法。线程池中的工作线程是通过该方法来启动并执行任务的。

shutdown取消任务
java 复制代码
public void shutdown() {
    super.shutdown();
}

@Override void onShutdown() {
    BlockingQueue<Runnable> q = super.getQueue();
    // shutdown的情况下是否继续执行现有延迟任务
    boolean keepDelayed =
        getExecuteExistingDelayedTasksAfterShutdownPolicy();
    // shutdown的情况下是否继续执行现有周期任务
    boolean keepPeriodic =
        getContinueExistingPeriodicTasksAfterShutdownPolicy();
    if (!keepDelayed && !keepPeriodic) {
        for (Object e : q.toArray())
            if (e instanceof RunnableScheduledFuture<?>)
                // 取消任务
                ((RunnableScheduledFuture<?>) e).cancel(false);
        // 清除等待队列
        q.clear();
    }
    else {
        // Traverse snapshot to avoid iterator exceptions
        for (Object e : q.toArray()) {
            if (e instanceof RunnableScheduledFuture) {
                RunnableScheduledFuture<?> t =
                    (RunnableScheduledFuture<?>)e;
                if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
                    t.isCancelled()) { 
                    // 如果任务已经取消,移除队列中的任务
                    if (q.remove(t))
                        t.cancel(false);
                }
            }
        }
    }
    // 终止线程
    tryTerminate();
}

线程池关闭方法调用了父类ThreadPoolExecutor的shutdown()方法,执行shutdown方法时调用了 onShutdown方法。onShutdown方法是ThreadPoolExecutor中的钩子方法,在ThreadPoolExecutor中什么都没有做。

通过这里我们可以看到通过参数(continueExistingPeriodicTasksAfterShutdownexecuteExistingDelayedTasksAfterShutdown)来决定线程池关闭后是否关闭已经存在的任务

总结

ScheduledThreadPoolExecutor继承自 ThreadPoolExecutor,使用了专门的任务类型(ScheduledFutureTask)和专门存储延迟队列(DelayedWorkQueue),为任务提供延迟或周期执行。

ScheduledThreadPoolExecutor被关闭(shutdown)之后支持可选的逻辑来决定是否继续运行周期或延迟任务。

相关推荐
Ling_suu14 分钟前
Spring——单元测试
java·spring·单元测试
ModelBulider15 分钟前
十三、注解配置SpringMVC
java·开发语言·数据库·sql·mysql
苹果酱056730 分钟前
C语言 char 字符串 - C语言零基础入门教程
java·开发语言·spring boot·mysql·中间件
csucoderlee37 分钟前
eclipse mat leak suspects report和 component report的区别
java·ide·eclipse
代码小鑫1 小时前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
bjxiaxueliang1 小时前
一文详解MacOS使用VSCode搭建SpringBoot+Gradle开发环境
spring boot·vscode·macos
训山1 小时前
4000字浅谈Java网络编程
java·开发语言·网络
VertexGeek1 小时前
Rust学习(四):作用域、所有权和生命周期:
java·学习·rust
豌豆花下猫1 小时前
REST API 已经 25 岁了:它是如何形成的,将来可能会怎样?
后端·python·ai
喔喔咿哈哈1 小时前
【手撕 Spring】 -- Bean 的创建以及获取
java·后端·spring·面试·开源·github