ScheduledThreadPoolExecutor原理源码解析

前言

ScheduledThreadPoolExecutor是用于处理定时任务的线程池,它继承ThreadPoolExecutor,拥有线程池的基础功能;而其定时处理的能力是来自于它定义的任务阻塞队列DelayedWorkQueue。通过上一篇文件得知线程池的运行原理,以及任务队列的作用,想要了解的可以移步线程池ThreadPoolExecutor原理解析

ScheduledThreadPoolExecutor源码解析

首先通过其入口函数分析schedule()函数

    // 延迟多长时间执行
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        // 构建包装task, decorateTask()默认返回第二个参数
        RunnableScheduledFuture<Void> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit),
                                          sequencer.getAndIncrement()));
        // 执行task
        delayedExecute(t);
        return t;
    }
        // 以指定速率循环执行
        public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0L)
            throw new IllegalArgumentException();
        // 和上面的区别:构造函数增加了period参数
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period),
                                          sequencer.getAndIncrement());
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

RunnableScheduledFuture的参数如下:
cammand: 真正执行task的
result: 执行的结果
tirggerTime: 触发task的事件
period: 循环执行的速率
sequenceNumber:触发task的序号
下面来看RunnableScheduledFuture的构造函数如下:

        ScheduledFutureTask(Runnable r, V result, long triggerTime, period, long sequenceNumber) {
            super(r, result);
            this.time = triggerTime;
            this.period = period;
            this.sequenceNumber = sequenceNumber;
        }

下面继续看delayedExecute()

    private void delayedExecute(RunnableScheduledFuture<?> task) {
        // 如果当前线程池处于SHUTDOWN状态,执行reject策略
        if (isShutdown())
            reject(task);
        else {
            // 否则,将task添加到task阻塞队列中
            super.getQueue().add(task);
            // 检查当前状态是否可以运行
            // 这里做了特殊检验:在SHUTDOWN状态,如果用户没有要求在SHUTDWON状态还可以接收新的task,那么下面会删除task
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                // 调用ThreadPoolExecutor的ensurePrestart()执行task
                ensurePrestart();
        }
    }

通过之前ThreadPoolExecutor的源码分析可知,在SHUTDOWN状态不会接收新的task;因此在这里添加task之后,做了第二次的校验。
在SHUTDOWN状态,如果用户没有显示指定可以在SHUTDOWN状态接收新的task,那么会将task删除掉的。

    boolean canRunInCurrentRunState(boolean periodic) {
        //continueExistingPeriodicTasksAfterShutdown和executeExistingDelayedTasksAfterShutdown 默认均为false
        return isRunningOrShutdown(periodic ?
                                   continueExistingPeriodicTasksAfterShutdown :
                                   executeExistingDelayedTasksAfterShutdown);
    }

    //isRunningOrShutdown()是ThreadPoolExecutor的函数
        final boolean isRunningOrShutdown(boolean shutdownOK) {
        int rs = runStateOf(ctl.get());
        return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
    }

//通过上面可知,只有当continueExistingPeriodicTasksAfterShutdown为true,才允许在SHUTDOWN状态,允许接收新task

ensurePrestart()方法如下:

    void ensurePrestart() {
        // 获取当前线程数量
        int wc = workerCountOf(ctl.get());
        // 如果线程数小于corePoolSize,创建线程执行
        if (wc < corePoolSize)
            addWorker(null, true);
        //下面是对应corePoolSize为0的情况
        else if (wc == 0)
            addWorker(null, false);
    }

addWorker()方法就不进行解析了,上面的链接中,可以查看。执行完addWorker()后,会执行runWorker()复用coreSize的线程执行其他task。
通过上面的分析,我们并没有发现时间调度的代码,其实这部分功能主要是通过其阻塞task队列DelayedWorkQueue实现,下面进行分析。
最后,来看运行的方法run()方法

        public void run() {
            boolean periodic = isPeriodic(); // period是否大于0
            // 是否允许在SHUTDOWN状态,执行task;默认不允许,会将task关闭
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            // 如果不是period任务(按照指定时间间隔执行),直接执行
            else if (!periodic)
                super.run();
            // 否则是定时任务,需要将task先reset
            else if (super.runAndReset()) {
                // 设置一次执行的时间,在当前时间基础加period时间
                setNextRunTime();
                // 重新执行任务
                reExecutePeriodic(outerTask);
            }
        }
        // 更新下一执行的时间
        private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p;
            else
                time = triggerTime(-p);
        }

            void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        // 判断当前state是否可以执行,RUNNING状态,或者SHUTDOWN状态用户手动设置continueExistingPeriodicTasksAfterShutdown为true
        if (canRunInCurrentRunState(true)) {
            // 允许执行,重新将task添加到队列中
            super.getQueue().add(task);
            // 添加完,状态改变,删除任务,并关闭任务
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            // 否则,小于corePoolSize,创建新的线程
            else
                ensurePrestart();
        }
    }

通过上面的分析,可以知道按照指定速率执行的任务是如何实现的。

DelayQueue时间调度实现

首先,我们知道线程池的阻塞队列是BlockQueue类型,因此DelayQueue继承自BlockQueue;因此,先分析BlockQueue。

BlockQueue

BlockQueue是一个接口,jdk总共提供了5个实现,分别实现不同的阻塞队列,下面给出五种队列及其功能。

  • ArrayBlockingQueue : 存储数据结构为数组,所包含的对象是按照FIFO排序的,可以设置其最大尺寸。
  • LinkedBlockingQueue:存储数据结构为链表,同样是FIFO排序,也可以设置其最大尺寸
  • PriorityBlockingQueue: 存储数据结构为数组,它的排序是通过构造函数的Comparator决定,它可以定义task的执行优先级,也可以设置最大尺寸。
  • SynchronousQueue:类似于生产者-消费者模型,它必须是存储和获取交替进行。

BlockQueue关键api说明:
插入数据:

  • add(e): 如果queue不能容纳当前对象,会抛出异常
  • offer(e): 如果queue不能容纳当前对象,返回false,也就是插入失败
  • put(e): 如果queue不能容纳当前对象,阻塞线程,直到queue有空余空间,再插入
  • offer(2, time, unit): 如果queue不能容纳当前对象, 阻塞线程time时间内,如果有空余空间,则插入,返回true;否则返回false

移除数据

  • remove(): 如果queue为空,那么会抛出异常;否则返回数据
  • poll(): 如果queue为空,那么返回null;否则返回数据
  • take(): 如果queue为空,那么阻塞线程直到queue不为空,取出数据返回。
  • poll(time, unit): 如果queue为空,那么zuse线程time时间内,如果有数据加入,那么直接返回;否则返回null。

DelayedWorkQueue

DelayedWorkQueue是继承BlockQueScheduledThreadPoolExecutor实现的阻塞队列。它的数据结构是堆,如果不熟悉堆的,推荐一篇文章纸上谈兵:堆
下面来看,DelayedWorkQueue的添加数据的api实现

        // 所有的插入数据的方法,实现都是调用的下面这个方法
        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;
                // 如果当前queue为空,直接将堆顶设置为当前对象,并且设置当前对象的heapIndex为堆顶index0
                if (i == 0) {
                    queue[0] = e;
                    setIndex(e, 0);
                } else {
                    //往堆中当前index添加对象,并对堆结构优先级,进行调整元素,向上进行调整
                    siftUp(i, e);
                }
                // 如果堆顶就是当前对象,那么通知阻塞的线程,当前queue中已经有数据了。
                if (queue[0] == e) {
                    leader = null;
                    available.signal();
                }
            } finally {
                lock.unlock();
            }
            return true;
        }

        public void put(Runnable e) {
            offer(e);
        }

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

        public boolean offer(Runnable e, long timeout, TimeUnit unit) {
            return offer(e);
        }

下面来看看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;
            }
            // 最后得出的k的位置就是当前对象的位置
            queue[k] = key;
            setIndex(key, k);
        }

下面来看,取数据的api:

        public RunnableScheduledFuture<?> poll() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                // 取堆顶元素
                RunnableScheduledFuture<?> first = queue[0];
                // 如果堆顶为null或者执行时间还没到,会返回null;否则返回当前task,并调整堆
                return (first == null || first.getDelay(NANOSECONDS) > 0)
                    ? null
                    : 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);
            // 将headpIndex设置为-1,也就是当前task不在堆中了
            setIndex(f, -1);
            return f;
        }

通过上面,数据是按照堆的结构存储,也就是根据执行的时间进行排序,每次去也是取堆顶元素。这样能保证task按照执行时间进行排序。
并且,取到task,但是task的执行时间还没有到,那么会返回null;上篇文件讲解线程池的原理,getTask()函数里面,在workQueue返回的null的情况,只要线程数没有达到corePoolSize,就会一直循环,直到有task可以执行。
我们直到getTask()主要使用的是take()方法,如果超过corePoolSize,会使用poll(time,unit),源码如下:

public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    RunnableScheduledFuture<?> first = queue[0];
                    //如果当前queue为空,那么会等有新的task加入,然后下次循环执行
                    if (first == null)
                        available.await();
                    else {
                        // 取还需延迟多长时间
                        long delay = first.getDelay(NANOSECONDS);
                        // 如果为0,直接返回task,并调整堆
                        if (delay <= 0L)
                            return finishPoll(first);
                        first = null; // don't retain ref while waiting
                        // 特殊情况
                        if (leader != null)
                            available.await();
                        else {
                            Thread thisThread = Thread.currentThread();
                            // 设置leader
                            leader = thisThread;
                            try {
                              // 延迟delay时间,重新获取task
                               available.awaitNanos(delay);
                            } finally {
                                // 如果线程没有改变,将leader清空
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && queue[0] != null)
                    available.signal();
                lock.unlock();
            }
        }

        public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit)
            throws InterruptedException {
            long nanos = unit.toNanos(timeout);
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    // 取堆顶元素
                    RunnableScheduledFuture<?> first = queue[0];
                    // queue为空
                    if (first == null) {
                        // keepAliveTime(nanos就是keepAliveTime时间)剩余时间为0,直接返回null
                        if (nanos <= 0L)
                            return null;
                        else
                            // 否则等待keepAliveTime时间
                            nanos = available.awaitNanos(nanos);
                    } else { // 如果堆顶不为null
                        // 获取task的延迟时间
                        long delay = first.getDelay(NANOSECONDS);
                        // delay时间到了执行时间,执行task
                        if (delay <= 0L)
                            return finishPoll(first);
                        // keepAliveTime时间到了,直接返回null
                        if (nanos <= 0L)
                            return null;
                        first = null; // don't retain ref while waiting
                        if (nanos < delay || leader != null)
                            nanos = available.awaitNanos(nanos);
                        else {
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                long timeLeft = available.awaitNanos(delay);
                                nanos -= delay - timeLeft;
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && queue[0] != null)
                    available.signal();
                lock.unlock();
            }
        }

通过上面可以得知,schedule操作是通过DelayWorkQueue的阻塞操作实现。

总结

  1. ScheduledThreadPoolExecutor继承了线程池原有的功能,线程复用和关闭的功能
  2. 在线程池原有的功能基础上,通过阻塞task队列实现schedule功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值