ScheduledThreadPoolExecutor 源码分析

概述

ScheduledThreadPoolExecutor是执行定时调度的线程池,它是ThreadPoolExecutor的子类。今天我们就一起看看ScheduledThreadPoolExecutor的源码,测试例子还是在github中

构造方法

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
复制代码

可以看到ScheduledThreadPoolExecutor默认最大线程数使用的无限线程,其实这个值没有效果,我们通过ThreadPoolExecutor源码知道,只有队列满了才会,创建核心线程数以外的线程处理任务。而DelayedWorkQueue又是无界的,DelayedWorkQueue我们先知道它是无界的有序的队列就好,稍后再详细分析,先一起看ScheduledThreadPoolExecutor常用的scheduleWithFixedDelay方法。

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();
    //通过提交的Runnable对象,构造一个ScheduledFutureTask对象
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(-delay));
    //t其实就是ScheduledFutureTask对象sft                                  
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    //延迟执行t
    delayedExecute(t);
    return t;
}
复制代码

可以看到就是将提交的Runnable对象封装成ScheduledFutureTask对象执行任务,一起看看ScheduledFutureTask类吧。 ScheduledFutureTask类结构

private class ScheduledFutureTask<V>
        extends FutureTask<V> implements RunnableScheduledFuture<V> {

    //加入线程吃的任务序号,用于任务的比较
    private final long sequenceNumber;

    //任务执行的时间(纳秒数)
    private long time;

    //重复任务的周期,以纳秒为单位(值为0时表示只执行一次)
    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();
    }
    。。。
}
复制代码

该类有两个重要的方法: compareTo用于排序确定在队列中的位置,run方法是任务执行时调用的方法。

public int compareTo(Delayed other) {
    if (other == this) // compare zero if same object
        return 0;
    if (other instanceof ScheduledFutureTask) {
        ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
        //比较任务执行的时间,时间相同时,根据sequenceNumber确定大小
        long diff = time - x.time;
        if (diff < 0)
            return -1;
        else if (diff > 0)
            return 1;
        else if (sequenceNumber < x.sequenceNumber)
            return -1;
        else
            return 1;
    }
    long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
复制代码
public void run() {
    //根据periodic是否为0判断是否是周期性任务
    boolean periodic = isPeriodic();
    //判断为非可执行任务状态时,取消任务
    //(注意进方法内部可以看到,判断中多一个关闭线程池后是否继续执行后续延迟任务或周期性任务,就是说ScheduledThreadPoolExecutor提供了一个关闭线程池,仍然执行定时任务的方法)
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    //不是周期性任务,直接执行    
    else if (!periodic)
        ScheduledFutureTask.super.run();
    //是周期性任务runAndReset方法会执行在执行结束时将任务的状态重置为NEW,便于下次再次执行    
    else if (ScheduledFutureTask.super.runAndReset()) {
        //设置下周执行的时间
        setNextRunTime();
        //再次执行周期行任务
        reExecutePeriodic(outerTask);
    }
}
复制代码

reExecutePeriodic方法 再次执行周期性任务

void reExecutePeriodic(RunnableScheduledFuture<?> task) {
    if (canRunInCurrentRunState(true)) {
        //将任务重新入队
        super.getQueue().add(task);
        //如果线程池已关闭,将任务取消
        if (!canRunInCurrentRunState(true) && remove(task))
            task.cancel(false);
        else
            ensurePrestart();//再次执行任务
    }
}
复制代码

ensurePrestart 方法

void ensurePrestart() {
    int wc = workerCountOf(ctl.get());
    //如果线程池小于核心线程数,新建线程执行,否则在队列中等待线程池中的线程调用getTask方法
    if (wc < corePoolSize)
        addWorker(null, true);
    else if (wc == 0)
        addWorker(null, false);
}
复制代码

任务的运行周期性执行逻辑我们已经看了,我们再次回到scheduleAtFixedRate方法中,看看任务的第一次执行,可以看到方法中有个delayedExecute方法。

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,如果线程数量小于核心线程数,则新建线程执行任务
    }
}
复制代码

至此,任务的执行逻辑已经清楚了,下面我们看看ScheduledThreadPoolExecutor中的阻塞队列和普通的队列有啥区别。
DelayedWorkQueue 有序、无界,其实就是一个专门接受RunnableScheduledFuture对象的DelayQueue队列。
在上面的分析中,可以看到任务执行前都是先入队,DelayedWorkQueue的add方法

public boolean add(Runnable e) {
    return offer(e);
}

public boolean offer(Runnable x) {
    if (x == null)
        throw new NullPointerException();
    RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        int i = size;
        //队列满时,将数组增加百分之五十
        if (i >= queue.length)
            grow();
        size = i + 1;
        //第一次入队时,放入队列的第一个位置
        if (i == 0) {
            queue[0] = e;
            setIndex(e, 0);
        } else {
            //将节点入队
            siftUp(i, e);
        }
        //如果新入队的节点成为了头节点,leader线程置为空,进行一次唤醒等待线程
        if (queue[0] == e) {
            leader = null;
            available.signal();
        }
    } finally {
        lock.unlock();
    }
    return true;
}
复制代码

siftUp方法

private void siftUp(int k, RunnableScheduledFuture<?> key) {
    //将等待队列数组构建成一个小顶堆
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        RunnableScheduledFuture<?> e = queue[parent];
        if (key.compareTo(e) >= 0)
            break;
        queue[k] = e;
        setIndex(e, k);
        k = parent;
    }
    queue[k] = key;
    setIndex(key, k);
}
复制代码

猜你喜欢

转载自juejin.im/post/5ee9ddcce51d4573cb31399d