深入理解Java并发编程(五):ScheduledThreadPoolExecutor分析

前言

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并且实现了ScheduledExecutorService,所以在分析ScheduledThreadPoolExecutor之前,需要先了解ThreadPoolExecutor的实现原理。

构造函数

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

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

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

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

通过以上4个构造函数可以看出,ScheduledThreadPoolExecutor最终都是通过父类ThreadPoolExecutor的构造函数来实现的。

同时,ScheduledThreadPoolExecutor所创建的线程池最大线程数量默认为Integer.MAX_VALUE,这是因为该线程池使用的队列DelayedWorkQueue当元素个数超过队列长度时,会自动扩容,每次扩容增加原来队列长度的一半数量。

ScheduledThreadPoolExecutor有以下几个成员变量:

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {

    private volatile boolean continueExistingPeriodicTasksAfterShutdown;
    private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
    private volatile boolean removeOnCancel = false;
    private static final AtomicLong sequencer = new AtomicLong();

    ...
}

continueExistingPeriodicTasksAfterShutdown:是否在线程池处于正在关闭的状态后继续执行周期任务,默认为false。

executeExistingDelayedTasksAfterShutdown:是否在线程池处于正在关闭的状态后继续执行非周期任务,默认为true。

removeOnCancel:当任务被取消后是否应当从任务队列中移除。

sequencer:用来给延迟时间相同的任务加上序列号。

任务队列:DelayedWorkQueue

static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {
    
    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 Thread leader = null;
    private final Condition available = lock.newCondition();
    
    ...
}

INITIAL_CAPACITY:初始队列大小。

queue:存储任务的队列,是一个数组实现。

lock:线程安全锁。

size:元素数量。

leader:队列头部元素所在线程,用于优化内部阻塞通知。

available:和lock对应的Condition,用于线程的等待和唤醒。

DelayedWorkQueue实际上是一个基于数组实现的优先队列,因为Delayed接口继承了Comparable接口,它根据这个任务的下次执行时间来比较大小,这样能够保证queue[0]位置上的元素是最近需要执行的任务。

offer()

public boolean offer(Runnable x) {
            if (x == null)
                throw new NullPointerException();
            RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
            final ReentrantLock lock = this.lock;
            // 1
            lock.lock();
            try {
                int i = size;
                // 2
                if (i >= queue.length)
                    grow();
                size = i + 1;
                // 3
                if (i == 0) {
                    queue[0] = e;
                    setIndex(e, 0);
                } else {
                    siftUp(i, e);
                }
                // 4
                if (queue[0] == e) {
                    leader = null;
                    available.signal();
                }
            } finally {
                // 5
                lock.unlock();
            }
            return true;
        }

将任务添加到队列中。

1、步骤1:上锁,确保线程安全。

2、步骤2:若队列长度不够,进行扩容,每次扩容至原长度的1.5倍。

3、步骤3:当i=0,即队列中没有任务时,将新添加的任务添加到队首,并设置heapIndex参数;否则调用siftUp()将任务根据优先级插入队列正确位置。

4、步骤4:当新添加的任务在队首时,设置leader为null,并唤醒一个线程。

5、步骤5:解锁。

take()

public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            // 1
            lock.lockInterruptibly();
            try {
                for (;;) {
                    // 2
                    RunnableScheduledFuture<?> first = queue[0];
                    if (first == null)
                        available.await();
                    else {
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)
                            // 3
                            return finishPoll(first);
                        first = null; // don't retain ref while waiting
                        if (leader != null)
                            available.await();
                        else {
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                available.awaitNanos(delay);
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                // 4
                if (leader == null && queue[0] != null)
                    available.signal();
                // 5
                lock.unlock();
            }
        }

1、步骤1:上锁,lock与lockInterruptibly比较区别在于lock优先考虑获取锁,待获取锁成功后,才响应中断;lockInterruptibly优先考虑响应中断,而不是响应锁的普通获取或重入获取。

2、步骤2:获取队首的任务对象,如果此时任务执行时间尚未达到或者任务队列为空,那么take()方法会让当前线程在Condition上等待,唤醒后会再次尝试进行上述操作。找到目标任务后,会调用finishPoll()方法执行后续操作。

3、步骤3:finishPoll()方法将目前队首的任务从队列中取出,并将队列重新排序,重新排序后队首会有新的任务。

4、步骤4:当队首有新的任务时,唤醒一个线程去尝试获取。

5、步骤5:解锁。

这里加入一个leader变量来分配阻塞队列中的任务,可以减少不必要的时间等待。比如说现在队列中的第一个任务1分钟后执行,那么用户提交新的任务时会不断的加入woker线程,如果新提交的任务都排在队列后面,也就是说新的woker现在都会取出这第一个任务进行执行延迟时间的等待,当该任务到触发时间时,会唤醒很多woker线程,这显然是没有必要的。

任务对象:ScheduledFutureTask

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

    private final long sequenceNumber;
    private long time;
    private final long period;
    RunnableScheduledFuture<V> outerTask = this;
    int heapIndex;

    ...
}

sequenceNumber:任务序列号。

time:开始执行的时间。

period:执行周期,为0时表示该任务不是周期任务。当period大于0时,表示下次执行任务的时间为 time + n * period(n为正整数,表示第几次任务,从0开始计数)。当period小于0时,会按照任务执行结束的时间往后-period纳秒为下次任务的执行时间。

outerTask:this的包装任务,用于周期任务的提交。

heapIndex:在任务队列中数组的位置,为-1时表示已经出队。

在ScheduledThreadPoolExecutor中,每个任务都被包装成ScheduledFutureTask,它继承了FutureTask类,实现了RunnableScheduledFuture接口。在上面的DelayedWorkQueue中,其数组的元素就是ScheduledFutureTask。

compareTo()

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重写了compareTo()方法,按照延迟时间排序,当需要延迟的时间一样时,比较序列号,先进队列的任务序列号较小。

run()

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

1、步骤1:判断任务在线程池状态=SHUTDOWN时是否继续执行。

2、步骤2:若不是周期性的任务,则调用父类的run()方法执行任务。

3、步骤3:若是周期性的任务,则调用父类的runAndReset方法执行任务,调用成功后,设置下次执行的时间,并加入到任务队列。

在调用run()方法的时候,会检测线程池的状态,如果线程池处于正在关闭的状态(比如调用shutdown方法但尚未执行完成),会调用canRunInCurrentRunState()方法,这个方法根据ScheduledThreadPoolExecutor的成员变量continueExistingPeriodicTasksAfterShutdown和executeExistingDelayedTasksAfterShutdown决定是否继续执行。这两个成员变量我们可以自行设置,不设置则使用默认值。

boolean canRunInCurrentRunState(boolean periodic) {
        return isRunningOrShutdown(periodic ?
                                   continueExistingPeriodicTasksAfterShutdown :
                                   executeExistingDelayedTasksAfterShutdown);
    }

setNextRunTime()

private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p;
            else
                time = triggerTime(-p);
        }

setNextRunTime()方法会根据周期任务的执行策略来算出下次执行的时间,当period为正数时,说明用户调用的是scheduleAtFixedRate()提交的周期任务,反之则是调用的scheduleWithFixedDelay()提交的任务。前者会严格按照每隔时间period纳秒就执行一次,后者会根据任务实际的完成时间为起点往后推triggerTime纳秒作为下次的执行时间。

cancel()

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

cancel()方法可以取消这个任务,它首先会调用父类FutureTask的cancel方法,然后根据ScheduledThreadPoolExecutor的成员变量removeOnCancel(默认为false)决定是否从任务队列中移除这个任务。

任务提交

除了execute()、submit()方法,ScheduledThreadPoolExecutor还实现了ScheduledExecutorService接口的schedule()、scheduleAtFixedRate()、scheduleWithFixedDelay()方法,这些方法都能用于提交任务。execute()、submit()方法也是调用schedule()方法实现的。

schedule()

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

任务在指定延迟时间到达后触发,只会执行一次。

1、步骤1:参数校验,command和unit不能为null。

2、步骤2:将任务包装成ScheduledFutureTask对象,当command为Runnable时,会将其转换成RunnableAdapter对象,RunnableAdapter实现了Callable接口,重写了call(),可以直接被调用,返回null。

3、decorateTask()是一个空方法,留给子类实现。

4、步骤3:将包装好的任务添加到延迟队列中,并且新建worker线程,保证有线程执行任务。

delayedExecute()

private void delayedExecute(RunnableScheduledFuture<?> task) {
        // 1
        if (isShutdown())
            reject(task);
        else {
            // 2
            super.getQueue().add(task);
            // 3
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                // 4
                ensurePrestart();
        }
    }

添加任务到延迟队列中,并且新建worker线程。

1、步骤1:判断线程池状态,若不为RUNNING,则拒绝任务。

2、步骤2:将任务添加到队列中,最终调用的是offer()方法。

3、步骤3:双重校验,判断当状态为SHUTDOWN时是否继续执行任务,是则进行步骤4,否则移除任务,并取消任务。

4、步骤4:保证有线程执行任务。

scheduleAtFixedRate()、scheduleWithFixedDelay()

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

scheduleAtFixedRate()、scheduleWithFixedDelay()这两个任务提交方法,核心区别就在于scheduleAtFixedRate方法提交任务时,任务后续执行的延迟时间都已经确定好了,分别是initialDelay,initialDelay + period,initialDelay + 2 * period以此类推;而调用scheduleWithFixedDelay方法提交任务时,第一次执行的延迟时间为initialDelay,后面的每次执行时间都是在前一次任务执行完成以后的时间点上面加上period延迟执行。

线程池关闭

ScheduledThreadPoolExecutor的shutdown方法默认调用的是父类的shutdown方法,它和父类ThreadPoolExecutor的区别在于它重写了onShutdown()方法,这个方法在父类中的实现为空。

onShutdown()方法中,若continueExistingPeriodicTasksAfterShutdown和executeExistingDelayedTasksAfterShutdown都为false,则取消所有任务,并且清空队列,否则根据规则取消任务。

@Override 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();
    }

对于shutdownNow方法,其执行策略和ThreadPoolExecutor相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值