ScheduledThreadPoolExecutor原理分析

今天学习了cheduledThreadPoolExecutor线程池,这里记录一下学习笔记,有错误,还望指出。

ScheduledThreadPoolExecutor类分析

定时线程池类的类结构图
在这里插入图片描述

它用来处理延时任务或定时任务。

在这里插入图片描述

提交任务的方式

它接收SchduledFutureTask类型的任务,是线程池调度任务的最小单位,有三种提交任务的方式:
在这里插入图片描述

  1. schedule
  2. scheduleAtFixedRate
  3. scheduleWithFixedDelay

它采用DelayQueue存储等待的任务

  1. DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若 time相同则根据sequenceNumber排序;
  2. DelayQueue也是一个无界队列;

SchduledFutureTask

从提交任务方法中可以看到command封装一层为ScheduledFutureTask

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

SchduledFutureTask接收的参数(成员变量):
在这里插入图片描述

 - private long time:任务开始的时间 
 - private final long sequenceNumber;:任务的序号 
 - private final long period:任务执行的时间间隔

工作线程的执行过程:

  • 工作线程会从DelayQueue取已经到期的任务去执行;
  • 执行结束后重新设置任务的到期时间,再次放回DelayQueue

ScheduledThreadPoolExecutor会把待执行的任务放到工作队列DelayQueue中, DelayQueue封装了一个PriorityQueue,PriorityQueue会对队列中的 ScheduledFutureTask进行排序,具体的排序算法实现如下:

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;
        }
  1. 首先按照time排序,time小的排在前面,time大的排在后面;
  2.  如果time相同,按照sequenceNumber排序,sequenceNumber小的排在前 面,sequenceNumber大的排在后面,换句话说,如果两个task的执行时间相同, 优先执行先提交的task
    

SchduledFutureTask之run方法实现

run方法是调度task的核心,task的执行实际上是run方法的执行。

	public void run() {
            boolean periodic = isPeriodic();
            //如果当前线程池已经不支持执行任务,则取消
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
                //如果不需要周期性执行,则直接执行run方法然后结束
            else if (!periodic)
                ScheduledFutureTask.super.run();
                //如果需要周期执行,则在执行完任务以后,设置下一次执行时间
            else if (ScheduledFutureTask.super.runAndReset()) {
            	// 计算下次执行该任务的时间
                setNextRunTime();
                //重复执行任务
                reExecutePeriodic(outerTask);
            }
        }
    }
  1. 如果当前线程池运行状态不可以执行任务,取消该任务,然后直接返回,否则执行 步骤2;
  2. 如果不是周期性任务,调用FutureTask中的run方法执行,会设置执行结果,然后 直接返回,否则执行步骤3;
  3. 如果是周期性任务,调用FutureTask中的runAndReset方法执行,不会设置执行 结果,然后直接返回,否则执行步骤4和步骤5;
  4. 计算下次执行该任务的具体时间;
  5. 重复执行任务。

reExecutePeriodic方法

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

该方法和delayedExecute方法类似,不同的是:

  1. 由于调用reExecutePeriodic方法时已经执行过一次周期性任务了,所以不会 reject当前任务;
  2. 传入的任务一定是周期性任务。

线程池任务的提交

首先是schedule方法,该方法是指任务在指定延迟时间到达后触发,只会执行一次。

public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        //参数校验
        if (command == null || unit == null)
            throw new NullPointerException();
        //这里是一个嵌套结构,首先把用户提交的任务包装成ScheduledFutureTask 8 //然后在调用decorateTask进行包装,该方法是留给用户去扩展的,默认是个 空方法
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit)));
        //包装好任务以后,就进行提交了                                  
        delayedExecute(t);
        return t;
    }

任务提交方法

private void delayedExecute(RunnableScheduledFuture<?> task) {
		//如果线程池已经关闭,则使用拒绝策略把提交任务拒绝掉
        if (isShutdown())
            reject(task);
        else {
        	//与ThreadPoolExecutor不同,这里直接把任务加入延迟队列
            super.getQueue().add(task);
            //如果当前状态无法执行任务,则取消
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
            	 //这里是增加一个worker线程,避免提交的任务没有worker去执行
            	 //原因就是该类没有像ThreadPoolExecutor一样,woker满了才放入队列
                ensurePrestart();
        }
    }

DelayedWorkQueue

ScheduledThreadPoolExecutor之所以要自己实现阻塞的工作队列,是因为 ScheduledThreadPoolExecutor要求的工作队列有些特殊。
DelayedWorkQueue是一个基于堆的数据结构,类似于DelayQueue和 PriorityQueue。在执行定时任务的时候,每个任务的执行时间都不同,所以 DelayedWorkQueue的工作就是按照执行时间的升序来排列,执行时间距离当前时间越近 的任务在队列的前面(注意:这里的顺序并不是绝对的,堆中的排序只保证了子节点的下次 执行时间要比父节点的下次执行时间要大,而叶子节点之间并不一定是顺序的,下文中会说明)。

堆结构如下图:
在这里插入图片描述
可见,DelayedWorkQueue是一个基于最小堆结构的队列。堆结构可以使用数 组表示,可以转换成如下的数组:

在这里插入图片描述
在这种结构中,可以发现有如下特性:
假设,索引值从0开始,子节点的索引值为k,父节点的索引值为p,则:

  1. 一个节点的左子节点的索引为:k = p * 2 + 1;
  2. 一个节点的右子节点的索引为:k = (p + 1) * 2;
  3. 一个节点的父节点的索引为:p = (k - 1) / 2。

为什么要使用DelayedWorkQueue呢?

定时任务执行时需要取出最近要执行的任务,所以任务在队列中每次出队时一定要是当 前队列中执行时间最靠前的,所以自然要使用优先级队列。
DelayedWorkQueue是一个优先级队列,它可以保证每次出队的任务都是当前队列中 执行时间最靠前的,由于它是基于堆结构的队列,堆结构在执行插入和删除操作时的最坏时 间复杂度是 O(logN)。


一切伟大的行动和思想,都有一个微不足道的开始。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值