ScheduledThreadPoolExecutor定时任务会消失
背景
我们通常使用Java原生ScheduledThreadPoolExecutor实现定时任务,当我们遇到定时任务不再执行情况时,一查会发现没有任何错误日志,线程还在但定时任务就是不执行,仿佛消失了,这是什么原因呢?
举例
下面例子代码,预期是每隔5秒执行一次DivideZero,但实际上只会执行1次,而且不会抛出任何异常。
public class DelayTaskException {
private static final ScheduledThreadPoolExecutor schedExecutor = new ScheduledThreadPoolExecutor(5);
private static class DivideZero implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("run");
int a = 1/0;
}
}
public static void main(String[] args) {
schedExecutor.scheduleWithFixedDelay(new DivideZero(), 5, 5, TimeUnit.SECONDS);
}
}
原因分析
ScheduledThreadPoolExecutor周期性定时任务实现原理:任务会被包裹成ScheduledFutureTask,然后丢到队列或者直接交给线程池线程首次执行,执行后设置重新执行时间,再丢回队列,之后由线程池线程取出来再执行,周而复始。
ScheduledFutureTask的run逻辑,
/**
* Overrides FutureTask version so as to reset/requeue if periodic.
*/
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的runAndReset逻辑,
/**
* Executes the computation without setting its result, and then
* resets this future to initial state, failing to do so if the
* computation encounters an exception or is cancelled. This is
* designed for use with tasks that intrinsically execute more
* than once.
*
* @return {@code true} if successfully run and reset
*/
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
//重点:一旦抛出Throwable(所有类型异常),ran就是false,最终方法返回也是false,不会把任务重新丢到队列。
setException(ex);
}
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}
通过上面2段源码逻辑,我们知道了一旦我们的任务抛出任何异常,任务就不会重新进入队列,就没法周期性执行了,任务消失了。
解决
解决方法很简单:run方法中捕获Throwable级别的所有异常,注意是Throwable不是Exception,只有Throwable能捕获到Exception和Error。
模板代码,
run(){
try{
//do job
}catch(Throwable e){
//自定义处理
}
}