使用ScheduledExecutorService执行定时任务时一定要注意各种异常捕获
近期一个项目有个定时任务阻塞住了,从日志里看没有任何异常产生,但就是定时不再执行了,进程还在,jstack看了下线程处于WAIT状态,但就是不再定时触发。于是拿代码分析了一下,代码原理很简单,拿ScheduledExecutorService.scheduleWithFixedDelay设定的定时任务,简化后类似这样:
public class Application { private static ScheduledExecutorService timer = Executors.newScheduledThreadPool(2); public static void main(String[] args) { timer.scheduleWithFixedDelay(() -> { try { //此处执行任务 } catch(Exception ex) { System.out.println(ex.getMessage()); } }, 0, 1, TimeUnit.SECONDS); } }
一般定时任务挂了,第一考虑的就是任务线程异常了,因为javadoc里关于scheduleWithFixedDelay有这样的警告:
当有异常产生时,后续定时任务就停掉了。但是代码里已经考虑了异常情况,做了try/catch,并且没有输出错误日志,只好修改代码尝试跟踪下线程执行结果:
public class Application { private static ScheduledExecutorService timer = Executors.newScheduledThreadPool(2); public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledFuture<?> handle = timer.scheduleWithFixedDelay(() -> { try { //此处执行任务 } catch(Exception ex) { System.out.println(ex.getMessage()); } }, 0, 1, TimeUnit.SECONDS); handle.get(); //如果线程执行异常,此处会抛出 } }
用ScheduledFuture跟踪,上面的测试程序当然不会报错,但在实际环境里打印了下面的异常:
Exception in thread "pool-1-thread-1" java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError
这下搞清楚了,因为-Xmx参数不合理导致线程在处理某个大数据时抛出OOM,这个错误没有被上面的try/catch捕获,导致定时任务挂掉。因此使用ScheduledExecutorService时一定要注意考虑各种可能的异常,不过对于OOM,即使捕获了能做的事也有限,但至少可以保证定时任务能继续,并且在日志里留下痕迹方便排查。