ScheduledThreadPoolExecutor实现周期调度的过程

本文研究ScheduledThreadPoolExecutor调度任务的过程,先看下面这段代码

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis());
    }
}, 0, 5, TimeUnit.MINUTES);       

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

首先通过Executors创建了ScheduledThreadPoolExecutor,然后提交了一个延迟为0分钟,周期为5分钟的任务。该任务会定时每5分钟执行一次。ScheduledThreadPoolExecutor是如何实现周期调度的呢?我们直接通过代码来分析,下面是scheduleAtFixedRate的方法定义(删除了异常判断)

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
    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;
}

该方法首先构造了ScheduledFutureTask,这个类是ScheduledThreadPoolExecutor的内部类(ScheduledThreadPoolExecutor还有一个内部类DelayedWorkQueue。ScheduledThreadPoolExecutor就是使用这两个类实现周期调度的)。我们先看下该类的构造方法,确定构造方法的每个参数的意义

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

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

time表示该任务的触发时间,period表示执行周期,callable就代表提交的业务任务。decorateTask方法默认返回的就是ScheduledFutureTask,所以ScheduledFutureTask.outerTask指向自己。我们接着看delayedExecute方法

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

重点看super.getQueue().add(task),该方法就是将任务添加进队列。通过ScheduledThreadPoolExecutor的构造方法可以知道该队列为DelayedWorkQueue

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

我假设你已经知道了线程池的执行过程,所以我们直接看DelayedWorkQueue的take方法

public RunnableScheduledFuture<?> take() throws InterruptedException {
    for (;;) {
         RunnableScheduledFuture<?> first = queue[0];
         if (first == null)
              available.await();
         else {
              long delay = first.getDelay(NANOSECONDS);
              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 = thisThread;
                  try {
                      available.awaitNanos(delay);
                  } 
              }
         }
     }                
}

DelayedWorkQueue是基于堆的数组结构,底层是数组。我们获取队列中的第一个任务,首先获取它的延迟getDelay。该方法返回的是一个任务的触发时间跟当前时间的差,单位为纳秒。如果返回的值小于等于0则立即返回;如果返回的值大于0,则让该线程等待延迟的时间。看到这儿,我们对调度执行的大体流程应该知晓了:一个带有触发时间的任务被加入队列,然后被线程池获取。获取的时候先判断是否已经到了触发时间,如果到了,立即执行;如果不到,就让当前线程等待相应时间。那其实还有问题,第一个任务我们看到是如何加到队列中的了,那之后的任务是如何创建和加到队列中的呢?新的任务是如何确定触发时间的呢?我们来看ScheduledFutureTask的run方法

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

我们直接忽略前两个判断,先看runAndReset方法

protected boolean runAndReset() {
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                c.call(); // don't set result
                ran = true;
            } catch (Throwable ex) {
                    setException(ex);
            }
        }
    } 
}

我们看到了callable的call方法被调用,上面已经说过callable就代表我们的业务任务,此时也就是我们的业务方法被执行了。

再看setNextRunTime方法

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

该方法就是在当前任务的触发时间上加上了周期,变成了下个任务的触发时间。

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

reExecutePeriodic方法中,我们又将修改了触发时间的任务加到了队列中。当然此处加的是outerTask,这样做具体有什么意义呢?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值