ScheduledThreadPoolExecutor JDK定时任务线程池

https://www.jianshu.com/p/18f4c95aca24

流程:

1 提交任务的时候,任务被包装成ScheduledFutureTask对象加入延迟队列并启动一个woker线程。

2 用户提交的任务加入延迟队列时,会按照执行时间进行排列,也就是说队列头的任务是需要最早执行的。而woker线程会从延迟队列中获取任务,如果已经到了任务的执行时间,则开始执行。否则阻塞等待剩余延迟时间后再尝试获取任务。

3 任务执行完成以后,如果该任务是一个需要周期性反复执行的任务,则计算好下次执行的时间后会重新加入到延迟队列中。

特性:

1 使用DelayedWorkQueue作为阻塞队列,并没有像ThreadPoolExecutor类一样开放给用户进行自定义设置。该队列是ScheduledThreadPoolExecutor类的核心组件,后面详细介绍。

2 这里没有向用户开放maximumPoolSize的设置,原因是DelayedWorkQueue中的元素在大于初始容量16时,会进行扩容,也就是说队列不会装满,maximumPoolSize参数即使设置了也不会生效。

3 worker线程没有回收时间,原因跟第2点一样,因为不会触发回收操作。所以这里的线程存活时间都设置为0。

继承了 ThreadPoolExecutor 实现了 ScheduledExecutorService 接口

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
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        //然后在调用decorateTask进行包装,该方法是留给用户去扩展的,默认是个空方法
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        //延迟执行
        delayedExecute(t);
        return t;
    }
    
 

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
                //这里是增加一个worker线程,避免提交的任务没有worker去执行
                //原因就是该类没有像ThreadPoolExecutor一样,woker满了才放入队列
                ensurePrestart();
        }
    }
    
 

线程启动后,由ScheduledThreadPoolExecutor的父类ThreadPoolExecutor接管。

ensurePrestart:

代码块

    void ensurePrestart() {
        int wc = workerCountOf(ctl.get());
        if (wc < corePoolSize)
            // 增加一个线程并启动
            addWorker(null, true);
        else if (wc == 0)
            addWorker(null, false);
    }
    
 

addWorker方法 会调用新创建线程的 start方法

线程获得cpu时间片后 执行线程的run方法 调用runworker方法

代码块

    public void run() {
            runWorker(this);
        }

        final void runWorker(Worker w) {

        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask; // task为null
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 执行getTask()方法从延迟队列中获得任务
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        // 执行run()方法
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
        
 
 

ScheduledFutureTask 内部类 中的run方法:

代码块

        // 重写run 方法 实现 到达周期后 任务入队
        public void run() {
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();

            // runAndReset执行最初设置的Runnable代码,若代码成功执行,则返回true,否则返回false(runnable中的代码拋出异常)。
            // 而只有当返回true时,执行reExecutePeriodic代码  把下次的任务添加进入队列 
            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();
        }
    }
    
 

reExecutePeriodic 以及delayedExecute 均调用了super.getQueue().add(task)行代码,

ScheduledThreadPoolExecutor类在内部自己实现了一个基于堆数据结构的延迟队列。add方法最终会落到offer方法中

代码块

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

        public boolean offer(Runnable x) {
            // 参数校验
            if (x == null)
                throw new NullPointerException();
            // 任务强转为RunnableScheduledFuture
            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);
                }
                //如果新加入的元素就是队列头,这里有两种情况
                //1.这是用户提交的第一个任务
                //2.新任务进行堆调整以后,排在队列头
                if (queue[0] == e) {
                    //这个变量起优化作用,后面说
                    leader = null;
                    //加入元素以后,唤醒worker线程
                    available.signal();
                }
            } finally {
                lock.unlock();
            }
            return true;
        }
        
 

runWorker()中调用的getTask() 方法:

代码块

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        // 循环重试获取任务
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            // 校验任务状态
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);
​
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // 线程数目 超过最大线程数  或 ((超过核心线程数 或 允许核心线程超时)且已超时 且 (线程数>1 或队列为空) ) 执行回收线程  
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
​
            try {
                Runnable r = timed ?
                    // 有超时时间的获取队列任务
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    // 无限期阻塞获取队列任务
                    workQueue.take();
                if (r != null)
                    return r;
                // 没有拿到任务  认为超时了
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
    
 

workQueue.take();: 延迟队列中的take 方法

代码块

public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                // 循环重试
                for (;;) {
                    // 仅仅获取队头元素  不出队列
                    RunnableScheduledFuture<?> first = queue[0];
                    if (first == null)
                        // 队列中任务为空 执行await() 阻塞等待  await() 会释放锁资源
                        available.await();
                    else {
                        //成功拿到任务

                         //计算任务执行时间,这个delay是 任务触发时间 减去当前时间
                        long delay = first.getDelay(NANOSECONDS);
                        // 到了触发时间,则执行出队操作
                        if (delay <= 0)
                            return finishPoll(first);
                        first = null; // don't retain ref while waiting
                        //这里表示该任务已经分配给了其他线程,当前线程等待唤醒就可以  其他线程拿到最新任务正在执行作为leader
                        if (leader != null)
                            available.await();
                        else {
                            //否则把给任务分配给当前线程
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                //当前线程等待任务剩余延迟时间
                                available.awaitNanos(delay);
                            } finally {
                                //这里线程醒来以后,什么时候leader会发生变化呢?
                                //就是上面的添加任务的时候
                                // 重置leader  重新循环拿任务
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                //如果队列不为空,则唤醒其他woker线程
                if (leader == null && queue[0] != null)
                    available.signal();
                lock.unlock();
            }
        }

        public long getDelay(TimeUnit unit) {
            return unit.convert(time - now(), NANOSECONDS);
        }
        
 

leader 变量优化 多个线程执行任务时的等待逻辑

这里为什么会加入一个leader变量来分配阻塞队列中的任务呢?原因是要减少不必要的时间等待。

比如说现在队列中的第一个任务1分钟后执行,那么用户提交新的任务时会不断的加入woker线程,如果新提交的任务都排在队列后面,

也就是说新的woker现在都会取出这第一个任务进行执行延迟时间的等待,当该任务到触发时间时,会唤醒很多woker线程,这显然是没有必要的。

发布了84 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/csdn_9527666/article/details/105219445