概述最近项目上报了一个重要的定时任务突然不执行了,很头疼。开发环境和测试环境都没有出现过这个问题。定时任务使用ScheduledThreadPoolExecutor。后来看代码的时候发现踩了个大坑。还原“大坑”的坑是,如果ScheduledThreadPoolExecutor中执行的任务失败,抛出异常,不仅不会打印异常堆栈信息,同时还会取消后续的调度,看看就好在例子中。@TestpublicvoidtestException()throwsInterruptedException{//创建一个定时任务线程池,有1个线程ScheduledExecutorServicescheduledExecutorService=Executors.newSingleThreadScheduledExecutor();//创建一个任务Runnablerunnable=newRunnable(){volatileintnum=0;@Overridepublicvoidrun(){num++;//模拟执行错误if(num>5){thrownewRuntimeException("执行错误");}log.info("execnum:[{}].....",num);}};//每1秒执行一次任务scheduledExecutorService.scheduleAtFixedRate(runnable,0,1,TimeUnit.SECONDS);Thread.sleep(10000);}运行结果:只执行了5次,就不会打印或执行了,因为报错任务,没有打印一次栈,导致调度任务取消,后果很严重。解决方案解决方案也很简单,只要用trycatch捕获异常即可。运行结果:不仅打印了异常栈,还进行了周期调度。比较推荐的做法是更好的建议在自己的项目中封装一个包装类,要求所有的调度都通过我们统一的包装类提交,如下代码:@Slf4jpublicclassRunnableWrapperimplementsRunnable{//真正要执行的线程任务私有可运行任务;//线程任务创建时间privatelongcreateTime;//线程池运行线程任务时的开始时间privatelongstartTime;//线程池运行线程任务的结束时间privatelongendTime;//线程信息privateStringtaskInfo;私人布尔showWaitLog;/***执行间隔多长时间,打印日志*/privatelongdurMs=1000L;//创建这个任务的时候,会设置它的创建时间//但是之后有可能这个任务提交到线程池之后,就会进入线程池的队列,排队publicRunnableWrapper(Runnabletask,StringtaskInfo){this.task=task;this.taskInfo=taskInfo;this.createTime=System.currentTimeMillis();}publicvoidsetShowWaitLog(booleanshowWaitLog){this.showWaitLog=showWaitLog;}publicvoidsetDurMs(longdurMs){this.durMs=durMs;}//当任务在线程池中排队时,这个run方法不会被运行//但是当任务完成排队并有机会在线程池中运行时,这个方法就会被调用//此时,可以设置线程任务的开始运行时间@Overridepublicvoidrun(){this.startTime=System.currentTimeMillis();//这里可以调用监控系统的API上报监控指标//使用线程任务的startTime-createTime,其实就是任务排队时间//这个在打印日志输出的同时,也可以输出到监控系统if(showWaitLog){log.info("任务信息:[{}],任务排队时间:[{}]ms",taskInfo,startTime-createTime);}//然后就可以调用被包裹的实际任务的run方法了try{task.run();}catch(Exceptione){log.error("运行任务错误",e);扔e;}//任务运行后This.endTime=System.currentTimeMillis()会设置任务运行的结束时间;//这里可以调用监控系统的API,上报监控指标//使用线程任务的endTime-startTime,其实就是任务运行时间//这里打印任务执行时间,也可以输出到监控系统}}}用途:我们也可以在封装类中封装各种监控行为,比如本例中的打印日志执行时间等原理。有没有想过为什么任务出错会导致异常打印失败,甚至调度失败?取消?让我们从源码入手一探究竟。下面是调度任务的入口方法。//ScheduledThreadPoolExecutor#scheduleAtFixedRatepublicSc??heduledFuture>scheduleAtFixedRate(Runnablecommand,longinitialDelay,longperiod,TimeUnitunit){if(command==null||unit==null)thrownewNullPointerException();}if(period<=0)thrownewIllegalArgumentException();//将执行任务和参数打包到ScheduledFutureTask对象中ScheduledFutureTask
