0%

ScheduledThreadPoolExecutor的坑

最近项目在一次更新后,发现定时任务无法执行了,根据日志推断,任务在执行到某一行后莫名其妙的就中断了,这不科学。排查了一下,发现这是ScheduledThreadPoolExecutor的一个坑。

起因

故障原因是由于NoClassDefDoundErorr引起的,由于依赖冲突,导致某个类找不到了,所以当代码执行到某行时,类加载器找不到这个类,就抛一个Error出来,任务线程也就中断了。按照一般的逻辑,本次定时任务被中断,到了下次开始运行的时间应该可以继续执行吧,这就是ScheduledThreadPoolExecutor的坑了:定时任务遇到异常导致线程死亡后,该任务将不会被继续执行。

解决办法

  1. 在向ScheduledThreadPoolExecutor提交任务时,可以拿到一个ScheduledFuture<?>,通过它可以捕捉到ExecutionException,也就是任务执行中抛出的异常(错误)。
  2. 在代码中try-catch-all。
    感觉第二种方式不太优雅。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}

注意将Runnable包装为ScheduledFutureTask的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 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()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}

问题就在ScheduledFutureTask.super.runAndReset(),我们一探究竟。

1
2
3
4
5
6
7
8
9
10
/**
* 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()

ScheduledThreadPoolExecutor直接使用了FutureTaskrunAndReset(),注意:

Failing to do so if the computation encounters an exception or is cancelled

所以如果需要支持在出现异常的情况下重复运行,需要自己手动来重写该方法。