最近写了个自定义的线程池,用于处理持续时间短、频次高的任务,逻辑上借鉴了CachedThreadPool,使用SynchronousQueue 作为任务的缓存队列,即实现没有任务进入队里,只要有空闲线程就执行任务。此外还自定义了拒接执行策略,由默认的异常拒绝执行,改为CallerRunsPolicy策略,并加了一行日志用作监控告警。
自定义线程池不是重点,本文的重点在线程池捕获异常的问题。
在实现该线程池的时候,笔者为其设置了一个ThreadFacotry,其中实现了UncaughtExceptionHandler
,但是测试该线程池的时候,笔者发现,有些情况线程执行异常了,但是没有触发UncaughtExceptionHandler
内部的逻辑(内部逻辑其实只是打日志监控)。
这就比较奇怪,后来对此作了查询,这里直接上结论和相关知识点,后面补一下来源:
如何捕获自定义线程池内的异常
对于线程池的execute(Runnable runnable)
函数而言
:设置UncaughtExceptionHandler
是有用的,也应当设定,这样可以处理记录线程内未捕获的异常。
对于submit(Callable<T> callable)
函数而言
设置UncaughtExceptionHandler
是没有用的,因为 该函数返回一个Future<T>
的对象,如果线程执行过程中有未捕获异常,会被包在Future<T>
对象中,不会抛出异常。对返回的Future调用get方法的时候,在get方法重新抛出包装之后的ExecutionException
。这个异常内部包含线程执行过程抛出的异常。这里的思路是 线程执行的异常,也是返回值的一部分,由获取返回值的时候再次抛出。
逻辑上这样放着,对submit方法不做特殊操作是可以的,但是如果有强迫症一点,想给线程池的submit方法设置底层的处理异常的方法呢,也有办法,是ThreadPoolExecutor#afterExecute
。如果执行之后有异常的话,该方法入参的Throwable参数不为null,可以在这里处理。
特例 SechduledThreadPool
SechduledThreadPool
是个异类,该线程池的execute
和submit
方法都委托给了sechdule
方法执行,会返回一个Future
,这就意味着,SechduledThreadPool#execute
提交的任务,如果执行线程异常了,UncaughtExceptionHandler
是捕获不到异常的,只能通过重写ThreadPoolExecutor#afterExecute
实现。
参考来源
- http://literatejava.com/threading/silent-thread-death-unhandled-exceptions/
- https://stackoverflow.com/questions/1838923/why-is-uncaughtexceptionhandler-not-called-by-executorservice
- https://my.oschina.net/lifany/blog/884002