最近在生产环境遇到一个比较极端的线程池吞异常问题,研究了下背后的原理,发现是静态块初始化异常抛出 ExceptionInInitializerError 导致的。这情景平时少见,在这里记录下已备忘。
问题描述
吞异常代码核心思想提炼后的样例是这样的:
@Slf4jpublic class ExceptionSingleton { private ExceptionSingleton() { // 单例实例化过程中抛出运行时异常 throw new RuntimeException("ExceptionSingleton constructor exception."); } private static class SingletonHolder { // 懒汉式单例 private volatile static ExceptionSingleton INSTANCE = new ExceptionSingleton(); } public static ExceptionSingleton getInstance() { return SingletonHolder.INSTANCE; } static class Processor implements Runnable { @Override public void run() { try { ExceptionSingleton.getInstance(); } catch (Exception e) { // 此处尝试捕获单例构造过程中抛出的RuntimeException,但其实无效 log.error("can not catch this exception here", e); } } } public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException { ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(new Processor();); Thread.currentThread().join(); }}复制代码
这段代码的期望思路是在线程池线程中捕获单例实例化所抛出的 RuntimeException,并打印日志。实际执行结果并没有打印任何日志,提交到线程池的 Processor 仿佛是凭空消失了一般。
要深究背后的原因,我们先来探讨几个知识点:
ExceptionInInitializerError
- 从类注释可以看出来,ExceptionInInitializerError 在静态类变量或者静态块初始化的时候会被抛出: /** * Signals that an unexpected exception has occurred in a static initializer. * An
ExceptionInInitializerError
is thrown to indicate that an * exception occurred during evaluation of a static initializer or the * initializer for a static variable. * *As of release 1.4, this exception has been retrofitted to conform to * the general purpose exception-chaining mechanism. The "saved throwable * object" that may be provided at construction tim