非受检异常需防止逃逸、沉默和不可追溯,应在入口处用try-catch兜底、为线程设专属处理器、主动唤醒异步异常、并提前拦截预防。

非受检异常(即 RuntimeException 及其子类)不会被编译器强制要求处理,但一旦未捕获,就可能让线程静默终止、任务丢失、资源泄漏,甚至在特定场景下引发连锁故障——看似没报错,实则系统已“半瘫痪”。防止这类异常演变成系统崩溃,关键不是消灭所有 RuntimeException,而是确保它**不逃逸、不沉默、可追溯**。
在入口处加一层 try-catch 兜底
每个独立执行单元(如 Thread.run()、Runnable.run()、Callable.call()、Activity.onCreate()、Service.onStartCommand())都应视为异常逃逸的“最后一道门”。不依赖全局处理器,而是在最外层主动包裹:
- 用
try { ... } catch (Throwable e) { ... }捕获所有异常(包括 Error),避免因只 catch Exception 而漏掉 OutOfMemoryError 等致命问题 - 捕获后做三件事:记录带堆栈的完整日志、释放本线程独占资源(如 Socket、Cursor、HandlerThread)、根据业务决定是恢复运行还是安全退出
- 不要简单吞掉异常或只打一行 log —— 静默失败比崩溃更难排查
为每个线程设置专属异常处理器
全局默认处理器(Thread.setDefaultUncaughtExceptionHandler)容易被覆盖或遗漏,尤其在多模块共存时。更可靠的做法是给每个线程实例单独绑定:
- 在线程构造完成、start() 之前调用
setUncaughtExceptionHandler - 处理器中至少输出线程名、异常类型、消息和简要堆栈,并触发告警(如上报监控平台)
- 若该线程持有关键资源(如数据库连接池、文件句柄),在处理器里显式清理,避免泄漏
异步任务必须主动“唤醒”异常
使用 Future 或 CompletableFuture 时,底层抛出的 RuntimeException 不会自动传播——它被封装后“沉睡”在 Future 对象里,除非你主动调用 get() 或注册回调:
- 提交 Callable 后,务必在合理时机调用
future.get(timeout, unit),并捕获ExecutionException和TimeoutException - 优先选用
CompletableFuture,用exceptionally()或handle()统一处理失败分支,避免漏写回调 - 所有异步回调(包括
thenApplyAsync)必须显式指定线程池,防止上下文丢失或异常误入主线程
提前拦截,比事后补救更有效
很多 RuntimeException 其实可预防。与其等空指针或数组越界发生,不如在源头加固:
- 对入参做防御性检查:用
Objects.requireNonNull()、StringUtils.isNotBlank()、集合判空等,失败时立即抛出带语义的异常 - 用
Optional替代可能为 null 的返回值,强制调用方处理“无值”场景 - 边界操作前校验索引:如
if (i >= 0 && i 比直接 <code>list.get(i)更可控 - 静态分析工具(如 SpotBugs、ErrorProne)能自动发现大量潜在空指针、资源未关闭等问题,集成进 CI 流程










