今天同事遇到一个问题,他写的 scheduledTask 不执行了,另一个一直在正常打印。针对这个问题,查找了,很多 jvm dump 文件,jstack 查看线程,但是线程都很正常,排除死锁,后来又想到竞争机制,但是这个也后面证明是不对的,不需要考虑,经过多次查资料和手动测试发现就是因为,执行过程中产生了异常,会造成定时任务不在执行。
上标注内容翻译为人话就是:如果执行过程中遇到了问题(error/exception),那么后面的定时任务也就不会继续执行了
这显然不符合预期啊。这简直就是 fuck egg 的事情,哪有程序不会遇到点 bug,遇到点异常呢?当时执行遇到异常,也许以后就好了呢?毕竟编程是一门神学,你不能因为一次异常,而放弃执行之后的定时任务啊!!!!
解决办法
那怎么解决这个问题呢。很显然,既然 ScheduledExecutorService 有可能在运行任务的过程中。任务(继承 Runnable 接口的)有可能抛出异常,那就 catch 这个异常呗。
方法 1
在 run 方法的外部,使用 try catch 语句 catch 可能的异常,仅仅 catch 异常(Exception)还是不够的,有可能还有 error,所以都需要 catch 的,代码如下
1 2 3 4 5 6 7 | try { throw new RuntimeException(“卧槽。。出现 bug 了,你竟然不 catch!!!!!”); } catch (Error e) { System.out.println(“error occurred, e=” + e); } catch (Exception e) { System.out.println(“exception occurred, e=” + e); } |
当然了,由于 Error 和 Exception 都继承了 Throwable,所以,只需要 catch Throwable 一个就可以了,所以以上代码可以简化为如下形式:
1 2 3 4 5 | try { throw new RuntimeException(“卧槽。。出现 bug 了,你竟然不 catch!!!!!”); } catch (Throwable t) { System.out.println(“some thing wrong happened, error=” + t); } |
但是在每个 run 方法中都要 try catch,也是很痛苦的事情,这得多了多少代码啊!!!此时,方法 2 就要现身了!!!
方法 2
编写一个 wrap 类,封装这个 ScheduledThreadPoolExecutor,在这个类里面进行 try/catch。这样外部就不用 try/catch 了;当然你也可以在这个类里面把异常继续向上抛出,如果选择继续把异常向上抛出,那么外部必须选择 try/catch 此异常,否则,还是会造成后续定时任务不会执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | package qhl.silver.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class WrappingScheduledExecutor extends ScheduledThreadPoolExecutor { public WrappingScheduledExecutor(int corePoolSize) { super(corePoolSize); } @Override public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { return super.scheduleAtFixedRate(wrapRunnable(command), initialDelay, period, unit); } @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { return super.scheduleWithFixedDelay(wrapRunnable(command), initialDelay, delay, unit); } private Runnable wrapRunnable(Runnable command) { return new LogOnExceptionRunnable(command); } private class LogOnExceptionRunnable implements Runnable { private Runnable theRunnable; public LogOnExceptionRunnable(Runnable theRunnable) { super(); this.theRunnable = theRunnable; } public void run() { try { theRunnable.run(); } catch (Throwable t) { System.err.println(“error in executing: " + theRunnable + “, the error = " + t); /** *重要,如果你选择继续向上抛出异常,则在外部必须能够 catch 住这个异常,否则还是会造成后续任务不会执行 _ IMPORTANT: if you thrown exception. then in the out, you have to try/catch the exception, _ otherwise, the executor will stop */ // throw new RuntimeException(e); } } } public static void main(String[] args) { new WrappingScheduledExecutor(1).scheduleAtFixedRate(new BadAssTask(), 1, 1, TimeUnit.SECONDS); } } |
问题延伸-看看知名开源软件怎么玩的
google guava 中的 WrappingExecutorService,WrappingScheduledExecutorService 也是简单的封装了,而且是 abstract 类,用户还无法直接使用,必须要有一个实现类 implement 这个类方可使用。思想与上述解决方案 2 一致。
画外音
线程池中的 ExecutorService 也是有这个问题的。请看如下代码,就如同注释中说的,如果不 try catch,则如果遇到问题就不会继续执行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package qhl.silver.ScheduledExecutorService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestExecutor { private ExecutorService executeProcessor; TestExecutor() { executeProcessor = Executors.newSingleThreadScheduledExecutor(); executeProcessor.execute(this::taskRun); } public void taskRun() { while (true) { System.out.println(“正常执行”); try { Thread.sleep(1 * 1000); throw new RuntimeException(“出错了,但是被我 catch 住之后,还是会继续执行的!!!”); } catch (Throwable e) { System.out.println(“error occurred = " + e); } System.out.println(“由于下面的错误没有被 catch,所以这个任务就不会被继续执行了”); // throw new RuntimeException(“出错了,没有被 catch,所以就不会继续执行了!!!”); } } public static void main(String args[]) { new TestExecutor(); } } |
结论
凡事使用 ExecutorService 的,都要 try catch
参考文献
ScheduledExecutorService Exception handling
Mother F**k the ScheduledExecutorService!
这里补充一下 jvm 技术手段:
|
|
jstack -pid 查看线程状态。