章
目
录
最近在开发项目遇到一个问题就是关ScheduledThreadPoolExecutor线程池在周期性执行任务代码时,当任务代码中存在阻塞情况,定时任务是否还会继续执行下一次的问题。为此潘老师做了一个简单的演示验证,效果如下。
首选潘老师说下结论,就是如果ScheduledThreadPoolExecutor线程池的周期性执行任务代码中存在阻塞代码,无论是scheduleWithFixedDelay
还是scheduleWithFixedRate
定时任务在阻塞期间都不会继续执行下一次。但是两者在使用方面还是有点区别,后面会说明下,接下来潘老师以scheduleWithFixedDelay来说下演示结果和原因:
演示代码
这里我们使用核心线程数(corePoolSize)为1的周期性线程池进行演示,使用ArrayBlockingQueue
模拟阻塞,代码如下:
public class Test { private static final ScheduledThreadPoolExecutor SCHEDULED_EXECUTOR = new ScheduledThreadPoolExecutor(1); public static void main(String[] args) { ArrayBlockingQueue queue = new ArrayBlockingQueue(16); SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> { System.out.println("开始取队列数据..."); try { queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("成功取出数据..."); }, 0, 3, TimeUnit.SECONDS); } }
我们会发现当ArrayBlockingQueue对列没有数据可以获取实现阻塞时,定时任务就停止了:
如果我们在ArrayBlockingQueue中预先放置两个数据,会定时获取出这两个数据,当获取第3个数据就会阻塞,定时任务也会终止,直到有数据获取到才会恢复定时任务。
另外还有一个误区就是如果我们把线程池核心数量调整为2甚至更多,当第一个线程的业务代码实现阻塞,很多同学会误以为当周期性时间到了会启动空闲的线程来继续该任务,也是错误的,每个线程之间是独立的,线程池也不会这样去分配周期性任务,只有当你再调用scheduleWithFixedDelay
方法时(即提交新任务),才会继续分配线程池中的空闲线程,不要把这个搞混了。
说下原因解析
ScheduledThreadPoolExecutor线程池的周期性执行任务代码中存在阻塞代码,定时任务不会继续执行下一次,原因就在于ScheduledThreadPoolExecutor的内部代码设计,也就是要了解它的周期性执行原理。
ScheduledThreadPoolExecutor周期性定时任务实现原理如下:任务会被包裹成ScheduledFutureTask,然后丢到队列或者直接交给线程池线程首次执行,执行后设置重新执行时间,再丢回队列,之后由线程池线程取出来再执行,周而复始。核心代码如下:
public void run() { boolean periodic = isPeriodic(); if (!canRunInCurrentRunState(periodic)) cancel(false); else if (!periodic)//一次性执行 ScheduledFutureTask.super.run(); else if (ScheduledFutureTask.super.runAndReset()) {//周期性执行。注意下面逻辑是否执行依赖runAndReset方法返回 //设置下次执行时间 setNextRunTime(); //重新把任务丢到队列 reExecutePeriodic(outerTask); } }
当执行是周期性执行时会走ScheduledFutureTask.super.runAndReset()
,而当我们的周期性业务代码出现阻塞时,runAndReset方法也会阻塞,就不会继续走到setNextRunTime();
方法,也就不会有下一次执行时间的,则不会继续执行代码。看到这里我想大家也就都明白了,另外再说一点,就是如果你的业务代码运行抛出了异常,会直接中断周期性任务,解决办法就是内部try catch掉即可。
scheduleWithFixedDelay和scheduleWithFixedRate表现差异
1)当前任务执行时间小于间隔时间,每次到点即执行;
2)当前任务执行时间大于等于间隔时间,上一次任务执行完成后就立即执行下一次任务。相当于连续执行了。
每当上次任务执行完毕后,间隔一段时间执行。不管当前任务执行时间大于、等于还是小于间隔时间,执行效果都是一样的。
总结
多线程和线线程池的使用,还是要多加小心为好,如果不确定就最好亲自验证下,以防出现意料之外的错误。现在你对ScheduledThreadPoolExecutor周期性任务代码阻塞定时任务还会继续执行下一次吗这个问题,应该有了深刻的认识了吧。