线程池ScheduledThreadPoolExecutor源码解析

版权声明:感谢您的阅读,欢迎讨论并指出不足,可自由转载,但请注明出处和作者 https://blog.csdn.net/qq_39470742/article/details/88989534

一、简介

ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,继承了父类对线程的管理维护功能,通过还可以执行延迟和定时任务。

/*
     * This class specializes ThreadPoolExecutor implementation by
     *
     * 1. Using a custom task type, ScheduledFutureTask for
     *    tasks, even those that don't require scheduling (i.e.,
     *    those submitted using ExecutorService execute, not
     *    ScheduledExecutorService methods) which are treated as
     *    delayed tasks with a delay of zero.
     *
     * 2. Using a custom queue (DelayedWorkQueue), a variant of
     *    unbounded DelayQueue. The lack of capacity constraint and
     *    the fact that corePoolSize and maximumPoolSize are
     *    effectively identical simplifies some execution mechanics
     *    (see delayedExecute) compared to ThreadPoolExecutor.
     *
     * 3. Supporting optional run-after-shutdown parameters, which
     *    leads to overrides of shutdown methods to remove and cancel
     *    tasks that should NOT be run after shutdown, as well as
     *    different recheck logic when task (re)submission overlaps
     *    with a shutdown.
     *
     * 4. Task decoration methods to allow interception and
     *    instrumentation, which are needed because subclasses cannot
     *    otherwise override submit methods to get this effect. These
     *    don't have any impact on pool control logic though.
     */

根据注释,也可以了解其相对于ThreadPoolExecutor 的变化:

  1. 使用内部类ScheduledFutureTask封装任务
  2. 使用内部类DelayedWorkQueue作为线程池队列
  3. onShutdown方法基于参数配置化去处理shutdown后的任务
  4. 提供decorateTask方法作为ScheduledFutureTask的修饰方法,以便使用者进行扩展
    ScheduledThreadPoolExecutor类图

二、ScheduledThreadPoolExecutor属性

/**
 * False if should cancel/suppress periodic tasks on shutdown.
 */
 //shutdown后是否继续执行定时任务
 private volatile boolean continueExistingPeriodicTasksAfterShutdown;

 /**
  * False if should cancel non-periodic tasks on shutdown.
  */
  //shutdown后是否继续执行延迟任务
 private volatile boolean executeExistingDelayedTasksAfterShutdown = true;

 /**
  * True if ScheduledFutureTask.cancel should remove from queue
  */
  //cancle方法收费需要将该任务从队列移除
 private volatile boolean removeOnCancel = false;

 /**
  * Sequence number to break scheduling ties, and in turn to
  * guarantee FIFO order among tied entries.
  */
  //任务的序列号
 private static final AtomicLong sequencer = new AtomicLong();

三、ScheduledThreadPoolExecutor构造方法

构造方法

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

与父类的构造方法相比,最大线程数maximumPoolSize固定为Integer.MAX_VALUE,最大活跃时间keepAliveTime固定为0,队列workQueue固定为DelayedWorkQueue。

四、DelayedWorkQueue

DelayedWorkQueue是BlockingQueue的实现类,是专门为了ScheduledThreadPoolExecutor设计的队列,队列中的数据结构为二叉树,每个节点的值小于其子节点的值。初始容量为16,节点数量大于数组长度后触发扩容,容量变为原来的1.5倍。

1. 属性

private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue =
    new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
private int size = 0;
private final Condition available = lock.newCondition();

INITIAL_CAPACITY为初始容量,queue数组用于存放节点。lock锁和available用于控制并发和阻塞等待,size表示节点数量。

private Thread leader = null;

leader属性用于表示正在阻塞等待头结点的线程,是一种Leader-Follower模式的变种,可以最小化线程的等待时间。同样通过阻塞方式去获取头结点,那么leader线程的等待时间为头结点的延迟时间,其它线程则会陷入阻塞状态(available.await())。leader线程获取到头结点后需要发送信号唤醒其它线程(available.asignAll())。

2. 插入节点

以DelayedWorkQueue#offer(Runnable)为例,其它插入节点的方法都是通过调用该方法完成

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;
}

(1)DelayedWorkQueue#grow()

表示扩容,数组长度修改为原来的1.5倍后复制数组中的元素。

private void grow() {
  int oldCapacity = queue.length;
   int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
   if (newCapacity < 0) // overflow
       newCapacity = Integer.MAX_VALUE;
   queue = Arrays.copyOf(queue, newCapacity);
}

(2)DelayedWorkQueue#setIndex()

setIndex(e, 0)用于修改ScheduledFutureTask的heapIndex属性,表示该对象在队列里的下标。

private void setIndex(RunnableScheduledFuture<?> f, int idx) {
   if (f instanceof ScheduledFutureTask)
        ((ScheduledFutureTask)f).heapIndex = idx;
}

(3)DelayedWorkQueue#siftUp(int, RunnableScheduledFuture<?>)

用于在插入新节点后对二叉树进行调整,进行节点上移,保持其特性(节点的值小于子节点的值)不变。

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);
}

从插入的节点开始循环比较,节点小于父节点,则上移。

3. 删除节点

DelayedWorkQueue#remove(Runnable)为例,其它删除方法也是通过调用盖方法完成。

public boolean remove(Object x) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        int i = indexOf(x);
        //节点元素不存在
        if (i < 0)
            return false;
		//修改元素的heapIndex
        setIndex(queue[i], -1);
        int s = --size;
        //末节点作为替代节点
        RunnableScheduledFuture<?> replacement = queue[s];
        queue[s] = null;
        if (s != i) {
        	//下移,保证该节点的子孙节点保持特性
            siftDown(i, replacement);
            //上移,保证该节点的祖先节点保持特性
            //上移和下移不可能同时发生,替代节点大于子节点时下移,否则上移
            if (queue[i] == replacement)
                siftUp(i, replacement);
        }
        return true;
    } finally {
        lock.unlock();
    }
}

(1)DelayedWorkQueue#indexOf(Object)

查找对象在队列中的下标。

private int indexOf(Object x) {
    if (x != null) {
        if (x instanceof ScheduledFutureTask) {
            int i = ((ScheduledFutureTask) x).heapIndex;
            // Sanity check; x could conceivably be a
            // ScheduledFutureTask from some other pool.
            if (i >= 0 && i < size && queue[i] == x)
                return i;
        } else {
            for (int i = 0; i < size; i++)
                if (x.equals(queue[i]))
                    return i;
        }
    }
    return -1;
}

ScheduledFutureTask根据其heapIndex属性,其它对象则进行遍历比较。

(2)DelayedWorkQueue#siftDown(int, RunnableScheduledFuture<?>)

private void siftDown(int k, RunnableScheduledFuture<?> key) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        RunnableScheduledFuture<?> c = queue[child];
        int right = child + 1;
        if (right < size && c.compareTo(queue[right]) > 0)
            c = queue[child = right];
        if (key.compareTo(c) <= 0)
            break;
        queue[k] = c;
        setIndex(c, k);
        k = child;
    }
    queue[k] = key;
    setIndex(key, k);
}

循环,选择当前节点的子节点中较小的节点与当前节点进行比较,如果当前节点小于子节点,则下移。

(3)非阻塞获取头结点

DelayedWorkQueue#poll()

public RunnableScheduledFuture<?> poll() {
   final ReentrantLock lock = this.lock;
   lock.lock();
   try {
       RunnableScheduledFuture<?> first = queue[0];
       if (first == null || first.getDelay(NANOSECONDS) > 0)
           return null;
       else
           return finishPoll(first);
   } finally {
       lock.unlock();
   }
}
private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
    int s = --size;
    RunnableScheduledFuture<?> x = queue[s];
    queue[s] = null;
    if (s != 0)
        siftDown(0, x);
    setIndex(f, -1);
    return f;
}

可以看到
(1)队列中没有元素:返回null。
(2)头结点的延迟时间没到:返回null。
(3)头结点达到延迟时间:末节点作为替代节点下移调整二叉树结构后返回头结点。

(4)阻塞获取头结点

以DelayedWorkQueue#take()为例,DelayedWorkQueue#poll(long, TimeUnit)类似,ScheduledThreadPoolExecutor的延迟执行功能就是通过阻塞获取头结点实现的。

public RunnableScheduledFuture<?> take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            RunnableScheduledFuture<?> first = queue[0];
            if (first == null)
                available.await();
            else {
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                    return finishPoll(first);
                first = null; // don't retain ref while waiting
                if (leader != null)
                //已经存在leader线程,当前线程await阻塞
                    available.await();
                else {
                //当前线程作为leader线程,并制定头结点的延迟时间作为阻塞时间
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                    	//leader线程阻塞结束
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
    //leader线程没有阻塞,可以找到头结点,唤醒阻塞线程
        if (leader == null && queue[0] != null)
            available.signal();
        lock.unlock();
    }
}

五、ScheduledFutureTask

在ScheduledThreadPoolExecutor中,所有的任务都会被封装成ScheduledFutureTask,无论是runnable还是callable,无论是否需要延迟和定时。
ScheduledFutureTask类图

1. 属性

/** Sequence number to break ties FIFO */
//序列号
private final long sequenceNumber;

/** The time the task is enabled to execute in nanoTime units */
//执行时间
private long time;

/**
 * Period in nanoseconds for repeating tasks.  A positive
 * value indicates fixed-rate execution.  A negative value
 * indicates fixed-delay execution.  A value of 0 indicates a
 * non-repeating task.
 */
 //定时周期
private final long period;

/** The actual task to be re-enqueued by reExecutePeriodic */
//实际任务
RunnableScheduledFuture<V> outerTask = this;

/**
 * Index into delay queue, to support faster cancellation.
 */
 //在队列中的下标
int heapIndex;

2. ScheduledFutureTask#compareTo(Delayed)

public int compareTo(Delayed other) {
    if (other == this) // compare zero if same object
        return 0;
    if (other instanceof ScheduledFutureTask) {
        ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
        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;
}

ScheduledFutureTask需要放入DelayedWorkQueue,而DelayedWorkQueue是从小到大进行排序的。
在ScheduledFutureTask,其大小的排序根据执行时间time正序排列,如果相同,在按照序列号sequenceNumber 正序排列。

3. ScheduledFutureTask#run()

ScheduledFutureTask由任务封装而来,执行任务就是执行ScheduledFutureTask#run()方法,ScheduledThreadPoolExecutor的周期执行的功能也是在run方法中实现。

public void run() {
    boolean periodic = isPeriodic();
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    else if (!periodic)
        ScheduledFutureTask.super.run();
    else if (ScheduledFutureTask.super.runAndReset()) {
        setNextRunTime();
        reExecutePeriodic(outerTask);
    }
}

(1)判断任务是否周期执行

public boolean isPeriodic() {
    return period != 0;
}

(2)检查当前状态能否执行任务

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);
 }

线程池处于RUNNING则必然可以执行,SHUTDOWN状态则需要检查对应的参数设置。
(3)如果不能执行,取消任务

public boolean cancel(boolean mayInterruptIfRunning) {
    boolean cancelled = super.cancel(mayInterruptIfRunning);
    if (cancelled && removeOnCancel && heapIndex >= 0)
        remove(this);
    return cancelled;
}

执行任务的取消逻辑(FutureTask的cancel方法),然后根据removeOnCancel参数设置确定是否需要从队列移除。
(4)非周期任务直接执行
ScheduledFutureTask.super.run()即FutureTask的run方法。
(5)周期任务的执行
ScheduledFutureTask.super.runAndReset()即FutureTask的runAndReset方法,与run方法的不用就是正常完成后任务的状态不会变化,依旧是NEW,且返回值为成功或失败,不会设置result属性。
需要注意,如果本次任务执行出现异常,返回false,后续的该任务不会再执行。
(6)周期任务的下一次执行时间

private void setNextRunTime() {
    long p = period;
    if (p > 0)
        time += p;
    else
        time = triggerTime(-p);
}
long triggerTime(long delay) {
   	return now() +
       ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
private long overflowFree(long delay) {
    Delayed head = (Delayed) super.getQueue().peek();
    if (head != null) {
        long headDelay = head.getDelay(NANOSECONDS);
        //发生溢出
        if (headDelay < 0 && (delay - headDelay < 0))
            delay = Long.MAX_VALUE + headDelay;
    }
    return delay;
}

这儿可以看到,周期时间period有正有负,这是ScheduledThreadPoolExecutor的ScheduledAtFixedRate和ScheduledWithFixedDelay的方法区别,前者为正数,后者为负数。
正数时,下一次执行时间为原来的执行时间+周期,即以执行开始时间为基准。
负数时,不考虑溢出情况,下一次执行时间为当前时间+周期,即以执行结束时间为基准。
如果溢出,下一次执行时间为Long.MAX_VALUE + headDelay。
为什么要考虑溢出?
因为在任务加入队列时要将该任务与二叉树中的部分原有任务在siftUp中通过compareTo进行比较,compareTo比较的是time的差值,如果出现溢出,会导致队列排序不正常,任务的执行混乱的结果。
为什么只考虑向上溢出,不需要考虑向下溢出
因为compareTo中比较的是time的差值,time都是long类型的正数,两个long类型的正数相减显然不会出现向下溢出。
溢出的判断条件是什么?
compareTo中比较的是time的差值,相当于两个节点getDelay的差值。差值最大的必然的该任务与队列头结点任务的差值。因此要判断会不会出现溢出,只需要判断delay - headDelay会不会溢出,delay必然是正数,要出现溢出则headDelay必为负数(也就是任务可以出队列但没有出队列,任务没有及时得到执行)的情况,因此判断条件就是:headDelay < 0 且 (delay - headDelay > Long.MAX_VALUE),即headDelay < 0 && (delay - headDelay < 0)。
为什么delay < (Long.MAX_VALUE >> 1)时不需要考虑溢出?
没想明白
为什么period为正数时不需要考虑溢出?
因为conpareTo比较的是time的差,假设当前任务的time为A,当前队列头结点任务的time为B,因为A比B先出队列,所以A < B。显然A + period - B < period < Long.MAX_VALUE,不会出现溢出的情况。
(7)任务的下一次执行安排

void reExecutePeriodic(RunnableScheduledFuture<?> task) {
   if (canRunInCurrentRunState(true)) {
        super.getQueue().add(task);
        if (!canRunInCurrentRunState(true) && remove(task))
            task.cancel(false);
        else
            ensurePrestart();
    }
}

如果当前线程池状态可以执行周期任务,加入队列,并开启新线程。
FutureTask的解析可以参考线程池源码解析之FutureTask

六、ScheduledThreadPoolExecutor执行任务

在对DelayedWorkQueue和ScheduledFutureTask这两个内部类进行解析后,正式针对ScheduledThreadPoolExecutor的方法进行解析。

1. ScheduledThreadPoolExecutor#schedule

(1)延迟执行callable任务

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)));
    delayedExecute(t);
    return t;
}

(2)延迟执行runnable任务

public ScheduledFuture<?> schedule(Runnable command,
                                   long delay,
                                   TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    RunnableScheduledFuture<?> t = decorateTask(command,
        new ScheduledFutureTask<Void>(command, null,
                                      triggerTime(delay, unit)));
    delayedExecute(t);
    return t;
}

2. scheduleAtFixedRate

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。但是任务执行完成之后,下一次任务才能加入队列。所以准确得说,开始执行时间 = max(上一次执行开始时间 + period, 上一次执行结束时间)

3. 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();
    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;
}

延迟且周期执行,到点执行,固定的延迟时间,开始执行时间 = 上一次任务执行结束时间 + delay。
与scheduleAtFixedRate的区别是通过传入ScheduledFutureTask的period的正负情况确定的,具体实现方式已经在上文ScheduledFutureTask的分析中提到。

4. ScheduledFutureTask#decorateTask(Runnable, RunnableFutureTask)

protected <V> RunnableScheduledFuture<V> decorateTask(
    Runnable runnable, RunnableScheduledFuture<V> task) {
    return task;
}

没有做任何操作,直接将task返回。
该方法主要目的是用于子类扩展。

5. ScheduledFutureTask#delayedExecute(RunnableScheduledFuture)

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()方法开启线程。

6. ScheduledFutureTask#ensurePrestart()

void ensurePrestart() {
    int wc = workerCountOf(ctl.get());
    if (wc < corePoolSize)
        addWorker(null, true);
     //corePoolSize = 0的情况,至少开启一个线程
    else if (wc == 0)
        addWorker(null, false);
}

检验运行线程数量,开启新线程

7. ScheduledFutureTask#onShutdown()

线程池状态由RUNNING >> SHUTDOWN变化时的钩子函数。

void onShutdown() {
    BlockingQueue<Runnable> q = super.getQueue();
    boolean keepDelayed =
        getExecuteExistingDelayedTasksAfterShutdownPolicy();
    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()) { // also remove if already cancelled
                    if (q.remove(t))
                        t.cancel(false);
                }
            }
        }
    }
    tryTerminate();
}

keepDelayed :shutdown后是否继续执行延迟任务
keepPeriodic :shutdown后是否继续执行周期任务
将不需要执行的延迟任务和周期任务进行取消并从队列中移除。
最后在检查tryTerminate()方法。

猜你喜欢

转载自blog.csdn.net/qq_39470742/article/details/88989534