定时任务线程池 ScheduleThreadPoolExecutor 应用和源码分析

目录

1.ScheduleThreadPoolExecutor介绍

2.ScheduleThreadPoolExecutor应用

3. ScheduleThreadPoolExecutor源码

3.1 核心属性

3.2 schedule方法

3.3 scheduleAtFixedRate和scheduleWithFixedDelay方法

3.3.1 run方法


1.ScheduleThreadPoolExecutor介绍

ScheduleThreadPoolExecutor支持延迟执行以及周期性执行的功能,

延迟执行:基于延迟队列实现

周期性执行:当任务执行完之后,再次设置好延迟时间,将任务重新扔到延迟队列中执行

2.ScheduleThreadPoolExecutor应用

定时任务线程池的有参构造,调用了父类 ThreadPoolExecutor 的构造方法,

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}

它的构造方法最多可以设置三个参数

  • 核心线程数
  • 线程工厂
  • 拒绝策略

它默认使用延迟队列 DelayedWorkQueue,这是一个无界队列,既然是无界队列,那么就轮不到非核心线程来执行,所以不需要设置最大线程数,临时线程的存活时间,unit等等,,,

代码示例

public class ScheduleThreadPoolExecutorDemo {

    public static void main(String[] args) {

        ScheduledThreadPoolExecutor scheduleExecutor = new ScheduledThreadPoolExecutor(
            10,
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("myThread");
                    return thread;
                }
            },
            new ThreadPoolExecutor.AbortPolicy()
        );

        // ---------- 分别测试四种方式 ------------------------


        // 和线程池的execute没啥区别
        scheduleExecutor.execute(() -> {
            System.out.println("execute");
        });


        // 指定延迟时间执行
        System.out.println(System.currentTimeMillis());
        scheduleExecutor.schedule(() -> {
            System.out.println("schedule");
            System.out.println(System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);

        // 指定任务第一次执行的延迟时间,并且设置第一次之后的周期执行时间,
        // 周期时间是在任务开始时就计算,(并且是在原有的时间上做累加,不关注任务的执行时长)
        // 第一次 2s,之后每次都是 4s
        System.out.println(System.currentTimeMillis());
        scheduleExecutor.scheduleAtFixedRate(() -> {
            System.out.println("scheduleAtFixedRate");
            System.out.println(System.currentTimeMillis());
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 2, 3, TimeUnit.SECONDS);


        // 指定第一次的延迟时间,并且设置第一次之后的周期执行时间,
        // 周期时间是在任务结束后再计算下次的延迟时间
        // 第一次 2s,之后每次都是 7s
        System.out.println(System.currentTimeMillis());
        scheduleExecutor.scheduleWithFixedDelay(() -> {
            System.out.println("scheduleWithFixedDelay");
            System.out.println(System.currentTimeMillis());
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 2, 3, TimeUnit.SECONDS);
    }
}

3. ScheduleThreadPoolExecutor源码

3.1 核心属性

// 这里是针对任务取消时的一些业务判断会用到的标记

// 周期执行任务,shutdown之后,默认不执行
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
// 延迟执行任务,shutdown之后,默认执行
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
private volatile boolean removeOnCancel = false;

// 计数器,如果两个任务的执行时间节点一模一样,根据这个序列来判断谁先执行
private static final AtomicLong sequencer = new AtomicLong();

// 获取当前系统时间的毫秒值
final long now() {
    return System.nanoTime();
}

// 内部类,核心类之一
private class ScheduledFutureTask<V>
        extends FutureTask<V> implements RunnableScheduledFuture<V> {

    // 全局唯一的序列,如果两个任务时间一样,基于当前属性判断谁先执行
    private final long sequenceNumber;

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

    /**
     *  period == 0:执行一次的延迟任务
     *  period > 0:代表是scheduleAtFixedRate
     *  period < 0:代表是scheduleWithFixedDelay
     */
    private final long period;

    // 周期性执行时,需要将任务重新扔回阻塞队列,基础当前属性拿到任务,方便扔回阻塞队列
    RunnableScheduledFuture<V> outerTask = this;


    /**
     * 构建schedule方法的任务
     */
    ScheduledFutureTask(Runnable r, V result, long ns) {
        super(r, result);
        this.time = ns;
        this.period = 0;
        this.sequenceNumber = sequencer.getAndIncrement();
    }

    /**
     * 构建At和With任务的有参构造
     */  
    ScheduledFutureTask(Runnable r, V result, long ns, long period) {
        super(r, result);
        this.time = ns;
        this.period = period;
        this.sequenceNumber = sequencer.getAndIncrement();
    }

}   

// 内部类,核心类之一
static class DelayedWorkQueue extends AbstractQueue<Runnable> 
                implements BlockingQueue<Runnable> {
}

3.2 schedule方法

execute 方法也是执行schedule方法,只不过传入的delay==0,

该方法就是将任务和延迟时间封装到一起,并且将任务扔到阻塞队列中,再去创建工作线程去take阻塞队列,

// command: 任务
// delay: 延迟时间
// unit:延迟时间的单位
public ScheduledFuture<?> schedule(Runnable command,
                                   long delay,
                                   TimeUnit unit) {

    // 非空判断
    if (command == null || unit == null)
        throw new NullPointerException();

    // triggerTime: 计算延迟时间,最终返回的是当前系统时间 + 延迟时间
    // ScheduledFutureTask有参构造:将任务以及延迟时间封装到一起,并设置执行方式
    // decorateTask:可以动态修改任务的一个扩展口
    RunnableScheduledFuture<?> t = decorateTask(command,
        new ScheduledFutureTask<Void>(command, null,
                                      triggerTime(delay, unit)));

    // 执行延迟任务
    delayedExecute(t);

    // 返回futureTask
    return t;
}


private long triggerTime(long delay, TimeUnit unit) {
    return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}

long triggerTime(long delay) {
    return now() +
        ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}


/**
 * ScheduledFutureTask 有参构造
 * 
 */
ScheduledFutureTask(Runnable r, V result, long ns) {
    super(r, result);
    // 任务要执行的时间
    this.time = ns;
    // 执行一次的延迟任务
    this.period = 0;
    // 基于AtmoicLong生成的唯一序列
    this.sequenceNumber = sequencer.getAndIncrement();
}

/**
 * 执行延迟任务的操作
 * 
 */
private void delayedExecute(RunnableScheduledFuture<?> task) {
    // 判断线程池状态
    if (isShutdown())

        // shutdown,执行拒绝策略
        reject(task);
    else {

        // 状态正常RUNNING

        // 将任务扔到阻塞队列
        super.getQueue().add(task);

        // 再次检查状态,如果不正常,就将任务从队列中移除
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&
            remove(task))
            task.cancel(false);
        else

            // 状态正常,任务可以执行
            ensurePrestart();
    }
}


/**
 * 根据状态以及任务执行方式,判断任务是否可以执行
 * 线程池shutDown之后:
 * <p>
 *      周期任务: 默认不可以执行
 *      执行一次的延迟任务: 默认可以执行
 * </p>
 * 
 */
boolean canRunInCurrentRunState(boolean periodic) {
    return isRunningOrShutdown(periodic ?
                               continueExistingPeriodicTasksAfterShutdown :
                               executeExistingDelayedTasksAfterShutdown);
}

final boolean isRunningOrShutdown(boolean shutdownOK) {
    int rs = runStateOf(ctl.get());
    return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
}


/**
 * 从队列中移除任务
 * 
 */
public boolean remove(Runnable task) {

    // 从队列中移除任务,队列自身的remove加了锁
    boolean removed = workQueue.remove(task);

    // 再次查看线程池状态,查看是否需要进入 terminated
    tryTerminate();
    return removed;
}

 cancel 方法,执行了 futureTask 中的方法,需要先了解 futureTask 源码再看,,

3.3 scheduleAtFixedRate和scheduleWithFixedDelay方法

这两个方法几乎一样,看一个就行,

// command: 任务
// initialDelay: 第一次执行的延迟时间
// period: 任务的周期执行时间
// unit: 时间单位
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {

    // 非空校验
    if (command == null || unit == null)
        throw new NullPointerException();

    // 周期时间不允许小于0
    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;

    // 执行延迟任务的操作,和schedule一样
    delayedExecute(t);
    
    // 返回futureTask
    return t;
}


/**
 * 周期执行任务的有参构造
 * 
 */
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
    super(r, result);
    this.time = ns;
    this.period = period;
    this.sequenceNumber = sequencer.getAndIncrement();
}

延迟任务以及周期任务在执行时,都会调用当前任务的run方法,,

3.3.1 run方法

public void run() {
    // periodic == false:一次性延迟任务
    // periodic == true:周期任务
    boolean periodic = isPeriodic();

    // 根据状态判断能否执行任务
    // 周期执行:false   一次性任务:true
    if (!canRunInCurrentRunState(periodic))

        // futureTask相关
        cancel(false);

    // 判断是周期执行还是一次性任务
    else if (!periodic)

        // 一次性任务,让工作线程直接执行command
        ScheduledFutureTask.super.run();

    else if (ScheduledFutureTask.super.runAndReset()) {
        // 周期性任务

        // 设置下次任务执行的时间
        setNextRunTime();

        // 将任务重新扔回线程池做处理
        reExecutePeriodic(outerTask);
    }
}


private void setNextRunTime() {

    // 拿到period值,正数:At,负数:With
    long p = period;
    if (p > 0)

        // At
        // 拿着上一次的执行时间,直接追加上周期时间
        time += p;
    else

        // With
        // 拿到当前系统时间,追加上延迟时间
        time = triggerTime(-p);
}


void reExecutePeriodic(RunnableScheduledFuture<?> task) {

    // 根据线程池状态判断,是否可以执行
    if (canRunInCurrentRunState(true)) {

        // 将任务加入延迟队列
        super.getQueue().add(task);

        // DCL,判断线程池状态
        if (!canRunInCurrentRunState(true) && remove(task))
            task.cancel(false);
        else
            // 添加工作线程执行任务
            ensurePrestart();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Master_hl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值