Java调度线程池ScheduledThreadPoolExecutor源码分析(2)

通过上面的逻辑,我们把提交的任务成功加入到了延迟队列中,前面说了加入任务以后会开启一个woker线程,该线程的任务就是从延迟队列中不断取出任务执行。这些都是跟ThreadPoolExecutor相同的,我们看下从该延迟队列中获取元素的源码:

public RunnableScheduledFuture<?> take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { //取出队列中第一个元素,即最早需要执行的任务 RunnableScheduledFuture<?> first = queue[0]; //如果队列为空,则阻塞等待加入元素时唤醒 if (first == null) available.await(); else { //计算任务执行时间,这个delay是当前时间减去任务触发时间 long delay = first.getDelay(NANOSECONDS); //如果到了触发时间,则执行出队操作 if (delay <= 0) return finishPoll(first); first = null; //这里表示该任务已经分配给了其他线程,当前线程等待唤醒就可以 if (leader != null) available.await(); else { //否则把给任务分配给当前线程 Thread thisThread = Thread.currentThread(); leader = thisThread; try { //当前线程等待任务剩余延迟时间 available.awaitNanos(delay); } finally { //这里线程醒来以后,什么时候leader会发生变化呢? //就是上面的添加任务的时候 if (leader == thisThread) leader = null; } } } } } finally { //如果队列不为空,则唤醒其他woker线程 if (leader == null && queue[0] != null) available.signal(); lock.unlock(); } }

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

当任务被woker线程取出以后,会执行run方法,由于此时任务已经被包装成了ScheduledFutureTask对象,那我们来看下该类的run方法:

public void run() { boolean periodic = isPeriodic(); //如果当前线程池已经不支持执行任务,则取消 if (!canRunInCurrentRunState(periodic)) cancel(false); else if (!periodic) //如果不需要周期性执行,则直接执行run方法然后结束 ScheduledFutureTask.super.run(); else if (ScheduledFutureTask.super.runAndReset()) { //如果需要周期执行,则在执行完任务以后,设置下一次执行时间 setNextRunTime(); //把任务重新加入延迟队列 reExecutePeriodic(outerTask); } }

上面就是schedule方法完整的执行过程。

ScheduledThreadPoolExecutor类中关于周期性执行的任务提供了两个方法scheduleAtFixedRate跟scheduleWithFixedDelay,一起看下区别。

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)); //... } public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { //... ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), //二者唯一区别 unit.toNanos(-delay)); //.. }

前者把周期延迟时间传入ScheduledFutureTask中,而后者却设置成负数传入,区别在哪里呢?看下当任务执行完成以后的收尾工作中设置任务下次执行时间的方法setNextRunTime源码:

private void setNextRunTime() { long p = period; //大于0是scheduleAtFixedRate方法,表示执行时间是根据初始化参数计算的 if (p > 0) time += p; else //小于0是scheduleWithFixedDelay方法,表示执行时间是根据当前时间重新计算的 time = triggerTime(-p); }

也就是说当使用scheduleAtFixedRate方法提交任务时,任务后续执行的延迟时间都已经确定好了,分别是initialDelay,initialDelay + period,initialDelay + 2 * period以此类推。
而调用scheduleWithFixedDelay方法提交任务时,第一次执行的延迟时间为initialDelay,后面的每次执行时间都是在前一次任务执行完成以后的时间点上面加上period延迟执行。

三、总结

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/f25cb4ae62cc2a190b0d605d076d456e.html