非受检异常在多线程中仅终止抛出它的子线程且默认打印堆栈到system.err,易被忽略;需通过uncaughtexceptionhandler统一拦截,线程池须用自定义threadfactory设置,虚拟线程需用thread.builder配置且手动透传上下文。

非受检异常(如 NullPointerException、ArrayIndexOutOfBoundsException)在多线程中不会自动传播到主线程,也不会中断其他线程。它只会终止抛出它的那个子线程,并默认打印堆栈到 System.err——这个行为容易被忽略,尤其在高并发或日志密集场景下,异常信息可能被冲刷掉,导致故障难以定位。
为什么不能靠 try-catch 包裹 run() 方法
虽然可以在 Runnable.run() 内部加 try-catch 捕获非受检异常,但这存在明显缺陷:
- 每个任务都要手动写,重复且易遗漏
- 无法统一做日志格式、上下文透传(如 traceId)、监控上报
- 若 catch 后未重新抛出或处理,业务逻辑可能静默失败
- 对
Callable任务不适用——异常会被包装进ExecutionException,需靠Future.get()主动触发
UncaughtExceptionHandler 是统一拦截的关键
每个线程都可绑定一个未捕获异常处理器,当线程因非受检异常退出时,JVM 自动调用该处理器:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
-
Thread.setUncaughtExceptionHandler():为单个线程设置,适合手动创建的线程 -
Thread.setDefaultUncaughtExceptionHandler():设全局默认处理器,仅对未显式设置 handler 的线程生效,宜作兜底,不宜主责 - 处理器方法签名固定:
void uncaughtException(Thread t, Throwable e) - 注意:必须在线程启动前设置,启动后调用
setUncaughtExceptionHandler无效
线程池场景必须用自定义 ThreadFactory
使用 Executors.newFixedThreadPool() 等默认工厂创建的线程池,线程不带异常处理器,异常即丢失。正确做法是实现自己的 ThreadFactory:
- 在
newThread(Runnable r)中创建线程后,立即调用t.setUncaughtExceptionHandler(...) - 可复用
Executors.defaultThreadFactory()的命名、守护属性等基础能力 - Spring 环境下,
ThreadPoolTaskExecutor需通过setThreadFactory注入该工厂 - 确保审计日志包含:线程名、任务标识(如类+方法)、traceId(建议从父线程
InheritableThreadLocal透传)、完整堆栈(用logger.error("", e))
虚拟线程与平台线程的处理差异
虚拟线程(Project Loom)同样需要显式配置异常处理器,但方式略有不同:
- 不支持
setUncaughtExceptionHandler(),而是通过Thread.ofVirtual().uncaughtExceptionHandler(...)或Thread.Builder设置 - 虚拟线程不自动继承
InheritableThreadLocal,若需传递上下文(如 traceId),必须手动透传 - 异常处理器内严禁同步阻塞操作(如 HTTP 上报、DB 写入),避免拖慢线程退出甚至引发死锁
- 处理器自身绝不能抛出新异常,否则 JVM 会静默忽略,异常彻底丢失









