多线程的异常处理
在线程执行过程中,若需对异常进行额外处理,可借助 Java 提供的UncaughtExceptionHandler
机制。这是线程的一个子接口,当未捕获的异常导致线程中断时,JVM 会通过thread.getUncaughtExceptionHandler()
查询线程的异常处理器,并将线程和异常作为参数传递给uncaughtException
方法。
UncaughtExceptionHandler 核心原理
UncaughtExceptionHandler
是一个函数式接口,其核心定义如下:
1 2 3 4 5
| @FunctionalInterface public interface UncaughtExceptionHandler { void uncaughtException(Thread t, Throwable e); }
|
异常处理器的传递链
当线程抛出未捕获的异常时,JVM 会按以下顺序查找处理器:
- 线程自身的处理器:通过
thread.setUncaughtExceptionHandler()
设置的实例;
- 线程组的处理器:若线程未设置处理器,使用其所属 线程组(ThreadGroup) 的处理器;
- 全局默认处理器:若线程组也未设置,使用
Thread.setDefaultUncaughtExceptionHandler()
设置的全局处理器;
- 系统默认行为:若以上均未设置,打印异常堆栈到
System.err
。
线程组(ThreadGroup
)默认的异常处理逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class ThreadGroup implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { e.printStackTrace(); } } } }
|
自定义异常处理器
通过实现UncaughtExceptionHandler
接口,可自定义异常处理逻辑(如日志记录、报警、资源释放等)。
步骤 1:实现自定义处理器
1 2 3 4 5 6 7 8 9
| class MyExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.err.println("线程 [" + t.getName() + "] 异常退出:" + e.getMessage()); Logger.getLogger(getClass()).error("线程异常", e); } }
|
步骤 2:为线程绑定处理器
1 2 3 4 5 6 7 8 9 10 11
| public class TestExceptionHandler { public static void main(String[] args) { Thread thread = new Thread(() -> { throw new RuntimeException("测试异常"); }); thread.setUncaughtExceptionHandler(new MyExceptionHandler()); thread.start(); } }
|
输出示例:
1 2 3 4 5
| 线程 [Thread-0] 异常退出:测试异常 2023-10-15 14:30:45 ERROR TestExceptionHandler:23 - 线程异常 java.lang.RuntimeException: 测试异常 at TestExceptionHandler.lambda$main$0(TestExceptionHandler.java:10) ...
|
线程池中的异常处理
实际开发中,线程池是更常用的线程管理方式。但线程池中的异常处理与普通线程存在差异:
execute(Runnable)
:异常会直接抛出,可被UncaughtExceptionHandler
捕获;
submit(Runnable/Callable)
:异常会被封装在Future
中,需主动调用get()
触发。
submit 方法异常捕获机制解析
submit
方法将任务包装为FutureTask
,其run()
方法内部捕获了所有异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void run() { try { result = callable.call(); set(result); } catch (Throwable ex) { setException(ex); } }
protected void setException(Throwable t) { outcome = t; state = EXCEPTIONAL; }
|
只有调用Future.get()
时,异常才会被重新抛出:
1 2 3 4
| public V get() throws ExecutionException { if (state == EXCEPTIONAL) throw new ExecutionException(outcome); }
|
线程池异常处理的最佳实践
方式一:任务内部主动捕获异常
在任务逻辑中添加try-catch
块:
1 2 3 4 5 6 7 8 9
| executorService.submit(() -> { try { int result = 1 / 0; } catch (Exception e) { logger.error("任务执行失败", e); } });
|
方式二:重写线程池的afterExecute
方法
通过钩子方法统一处理异常:
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
| class CustomThreadPoolExecutor extends ThreadPoolExecutor { public CustomThreadPoolExecutor(...) { super(...); }
@Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t == null && r instanceof Future<?>) { try { Future<?> future = (Future<?>) r; if (future.isDone()) { future.get(); } } catch (CancellationException ce) { } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } if (t != null) { logger.error("线程池任务异常", t); } } }
|
方式三:自定义线程工厂
为线程池中的所有线程设置统一的异常处理器:
1 2 3 4 5 6 7 8 9 10 11 12
| ThreadFactory factory = new ThreadFactoryBuilder() .setNameFormat("custom-pool-%d") .setUncaughtExceptionHandler((t, e) -> { logger.error("线程 [{}] 异常: {}", t.getName(), e.getMessage(), e); }) .build();
ExecutorService executor = new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), factory );
|
全局异常处理器设置
为所有线程设置默认异常处理器:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler((t, e) -> { System.err.println("全局处理器捕获异常:线程 [" + t.getName() + "] " + e.getMessage()); });
Thread t = new Thread(() -> { throw new RuntimeException("全局处理器测试"); }); t.start(); }
|
五、异常处理最佳实践总结
- 优先使用线程池:通过
ThreadPoolExecutor
的afterExecute
方法统一处理异常;
- 任务粒度控制:每个任务应足够独立,避免因单个任务异常导致整个线程池崩溃;
- 主动检查 Future 结果:对
submit
提交的任务,及时调用Future.get()
获取结果;
- 结合日志与监控:异常发生时,记录完整堆栈信息并触发报警;
- 避免静默失败:所有异常都应被明确处理,防止资源泄漏或数据不一致。
v1.3.10