计划任务线程池ScheduledThreadPoolExecutor原理

Preview

部分内容来源:《深入浅出Java多线程》 - 计划任务

前置知识:Java线程池原理,不了解的同学可以看这个: Java线程池执行与线程复用的原理

JDK版本:OpenJDK16.0.2

使用样例

将消息(包含发送时间)存储在数据库中,用一个定时任务,每隔1秒检查数据库在当前时间有没有需要发送的消息:

private static final ScheduledExecutorService executor = 
    new ScheduledThreadPoolExecutor(1, Executors.defaultThreadFactory());private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args){
   
    // 新建一个固定延迟时间的计划任务
    // 新建任务1s以后,任务开始执行
    // 上一个任务执行完以后,等待2s,执行下一个任务
    System.err.printf("【%s】新建任务%n" , df.format(new Date()));
    executor.scheduleWithFixedDelay(new Runnable() {
   
        @Override
        public void run() {
   
            if (haveMsgAtCurrentTime()) {
   
                System.err.printf("【%s】大家注意了,我要发消息了%n" , df.format(new Date()));
            }
        }
    }, 1, 2, TimeUnit.SECONDS);
}public static boolean haveMsgAtCurrentTime(){
   
    // 查询数据库,有没有当前时间需要发送的消息
    // 这里省略实现,直接返回true
    return true;
}

输出:

【2021-10-12 20:27:35】新建任务
【2021-10-12 20:27:36】大家注意了,我要发消息了
【2021-10-12 20:27:38】大家注意了,我要发消息了
【2021-10-12 20:27:40】大家注意了,我要发消息了

计划任务的特性

计划任务分为两种:

  • 非周期性任务,这种任务只执行一次,需要在指定的时间运行

  • 周期性任务,这种任务要执行多次,周期性任务又可以分为两种

    • 固定频率:每隔一段时间,任务就执行一次,比如每五分钟执行一次
    • 固定间隔:两次任务的执行之间需要间隔一定的时间,比如本次任务执行后,等待五分钟,然后执行下一次任务

假如让我们自己来实现一个计划任务线程池,我们需要实现两个特性:

  1. 多次执行任务
  2. 在指定时间执行任务

如果只执行非周期性任务,只需要满足第二点特性就可以,但对于周期性任务,必须两个特性都要满足,可以说,只要线程池可以实现这两个特性,这个线程池就是计划任务线程池

所以,ScheduledThreadPoolExecutor的关键就在于,它是如何实现这两个特性的

下面,带着这两个疑问,我们来分析ScheduledThreadPoolExecutor的源码

在分析过程中,我们顺着线程池的使用方式来阅读源码,首先看一下线程池在提交任务时会做些什么,然后再看看任务在执行时又会做些什么

ScheduledThreadPoolExecutor

类结构

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
   
    
    // 计划任务线程池的构造方法之一
    // 注意,这里使用的workQueue是DelayedWorkQueue,关于这个队列的具体内容,我们后面再聊
    public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {
   
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
    }
}

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,这个类就是线程池,不多赘述

ScheduledThreadPoolExecutor还实现了ScheduledExecutorService接口,这个接口规定了一些方法签名,这些方法负责把周期性任务提交到线程池,源码如下

public interface ScheduledExecutorService extends ExecutorService {
   
    
    // 单次执行任务,无返回值
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
    
    // 单次执行任务,有返回值
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);// 多次执行任务,创建任务后,经过 initialDelay 时间,执行第一次任务
    // 此后,每隔 period 时间,执行一次任务,无论上一次任务是否完成,都会执行
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
    
    // 多次执行任务,创建任务后,经过 initialDelay 时间,执行第一次任务
    // 每次任务执行完成之后,间隔 delay 时间,才执行下一次任务
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

提交任务的四个方法

ScheduledExecutorService中制定了四个提交周期性任务,在ScheduledThreadPoolExecutor中的实现如下:

schedule(无返回值)
public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
   // 用于打破调度关系的序列号,保证绑定项之间的FIFO顺序
    private static final AtomicLong sequencer = new AtomicLong();
    
    // 单次执行任务,无返回值
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
   
        if (command == null || unit == null)
            throw new NullPointerException();
        // decorateTask:直接返回第二个参数
        // 在这里,会直接返回 new 出来的 ScheduledFutureTask 对象
        RunnableScheduledFuture<Void> t = decorateTask(command,
            // 创建任务,带有初始延时
            new ScheduledFutureTask<Void>(command, null,
                                          // triggerTime:根据delay、unit和当前系统时间,计算出第一次执行任务的时间
                                          triggerTime(delay, unit),
                                          // 序列号+1
                                          sequencer.getAndIncrement()));
        // 延期或周期性任务的主要方法
        delayedExecute(t);
        return t; 
    }
    
    // 直接返回第二个参数
    protected <V> RunnableScheduledFuture<V> decorateTask(
        Runnable runnable, RunnableScheduledFuture<V> task) {
   
        return task;
    }
}
schedule(有返回值)
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                       long delay,
                                       TimeUnit unit) {
   
    if (callable == null || unit == null)
        throw new NullPointerException();
    RunnableScheduledFuture<V> t = decorateTask(callable,
        new ScheduledFutureTask<V>(callable,
                                   triggerTime(delay, unit),
                                   sequencer.getAndIncrement()));
    delayedExecute(t);
    return t;
}
scheduledAtFixedRate
// 多次执行任务,创建任务后,经过 initialDelay 时间,执行第一次任务
// 此后,每隔 period 时间,执行一次任务,无论上一次任务是否完成,都会执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
   
    if (command == null || unit &
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ScheduledThreadPoolExecutorJava 中的一个线程池实现,它可以根据指定的延迟时间或定时周期执行任务。其原理主要包括以下几个方面: 1. 线程池管理:ScheduledThreadPoolExecutor 内部维护一个线程池,其中包含多个工作线程。这些工作线程可以并行执行提交的任务。 2. 任务队列:ScheduledThreadPoolExecutor 使用一个任务队列来存储提交的任务。当任务被提交时,会先进入任务队列中等待执行。 3. 任务调度:ScheduledThreadPoolExecutor 使用一个调度器来控制任务的执行时间。调度器会根据任务的延迟时间或定时周期,将任务从任务队列中取出,并分配给空闲的工作线程执行。 4. 线程池调度策略:ScheduledThreadPoolExecutor 提供了不同的调度策略,可以根据需要选择合适的策略。见的调度策略包括延迟执行、周期性执行、固定延迟执行等。 5. 线程池管理和任务调度的协调:ScheduledThreadPoolExecutor 通过线程池管理和任务调度的协调,实现了对延迟执行或定时周期执行任务的支持。它会根据需要动态地创建、销毁工作线程,并将任务分配给这些工作线程执行。 总之,ScheduledThreadPoolExecutor 通过线程池管理和任务调度的机制,实现了对延迟执行或定时周期执行任务的支持。这使得开发者可以方便地控制任务的执行时间,并且可以充分利用系统资源来提高任务执行的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值