当前位置: 首页 > news >正文

Java捕获子线程异常以及主线程感知子线程异常

Java捕获子线程异常以及主线程感知子线程异常

    • 主线程能直接捕获子线程的异常吗?
    • 为每个线程设置未捕获异常处理器 (UncaughtExceptionHandler)
    • 使用 Future 和 ExecutorService(最佳实践)
    • 在 run() 方法内部自行 try-catch
    • 总结与对比

主线程能直接捕获子线程的异常吗?

Java 的线程是独立执行的,每个线程都有自己的执行栈和方法调用。try-catch 块是基于当前线程的调用栈工作的。主线程的 try-catch 只能捕获发生在它自己线程内的异常,而子线程运行在另一个独立的调用栈上,其内部抛出的异常不会传播到主线程的调用栈中。

代码示例证明:

public class MainThreadCannotCatch {public static void main(String[] args) {try {// 主线程启动一个子线程Thread childThread = new Thread(() -> {// 这个异常发生在子线程内部throw new RuntimeException("子线程内部异常!");});childThread.start();// 主线程等待一下子线程结束(即使等待,也捕获不到)Thread.sleep(1000);System.out.println("主线程正常结束。");} catch (Exception e) { // 这里的 catch 块只会捕获主线程自己的异常(如 InterruptedException)// 绝对捕获不到上面子线程抛出的 RuntimeException!System.out.println("主线程捕获到异常: " + e.getMessage());}}
}

输出:

Exception in thread "Thread-0" java.lang.RuntimeException: 子线程内部异常!at MainThreadCannotCatch.lambda$main$0(MainThreadCannotCatch.java:8)at java.lang.Thread.run(Thread.java:748)
主线程正常结束。

可以看到,子线程异常导致线程崩溃并打印了栈轨迹,而主线程的 catch 块完全没有起作用,主线程正常执行完毕。

为每个线程设置未捕获异常处理器 (UncaughtExceptionHandler)

这是最推荐和标准的方式。你可以为单个线程或所有线程设置一个处理器,当线程因未捕获异常而即将终止时,JVM 会回调这个处理器。

public class UncaughtExceptionHandlerDemo {public static void main(String[] args) {// 创建一个线程Thread childThread = new Thread(() -> {System.out.println("子线程开始运行...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 模拟一个运行时异常throw new RuntimeException("子线程发生未知错误!");});// 为单个线程设置未捕获异常处理器childThread.setUncaughtExceptionHandler((thread, throwable) -> {// 这里是在主线程中定义逻辑,但由JVM在子线程终止前调用System.err.println("线程 '" + thread.getName() + “‘ 抛出了异常:+ throwable.getMessage());// 这里可以进行日志记录、报警、清理等操作// 注意:这个处理逻辑是在发生异常的线程上下文中执行的,而不是主线程。});// 也可以设置全局默认的处理器,捕获所有线程未处理的异常Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {System.err.println("[全局捕获器] 线程 " + thread.getName() + " 出错了: " + throwable.getMessage());});childThread.start();try {// 主线程等待子线程结束,以便观察输出childThread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程结束。");}
}

使用 Future 和 ExecutorService(最佳实践)

通过线程池提交任务(submit() 方法)会返回一个 Future 对象。调用 Future.get() 方法时,主线程会阻塞等待子线程执行完成,并且子线程中的任何异常都会被包装成 ExecutionException 重新抛出,从而可以在主线程中被捕获。

import java.util.concurrent.*;public class FutureAndExecutorDemo {public static void main(String[] args) {// 创建一个线程池ExecutorService executor = Executors.newSingleThreadExecutor();// 提交任务,得到 Future 对象Future<?> future = executor.submit(() -> {System.out.println("通过ExecutorService提交的任务开始执行...");throw new RuntimeException("任务执行失败!");});try {// 主线程在这里阻塞,等待任务执行结果future.get(); // 这一行会抛出 ExecutionException} catch (InterruptedException e) {// 处理中断异常e.printStackTrace();} catch (ExecutionException e) {// 这里才是关键!捕获由子线程异常包装而来的 ExecutionException// 通过 e.getCause() 获取子线程中抛出的原始异常Throwable originalException = e.getCause();System.out.println("主线程捕获到子线程的异常: " + originalException.getMessage());originalException.printStackTrace();} finally {// 关闭线程池executor.shutdown();}}
}

这是生产环境中最常用、最可控的方式,因为它结合了线程池管理和完善的异常处理机制。

在 run() 方法内部自行 try-catch

在最源头(run 方法内部)处理掉所有异常,防止异常抛出到线程机制中。

public class InternalTryCatch {public static void main(String[] args) {Thread childThread = new Thread(() -> {try {// 将所有业务逻辑放在try块中System.out.println("子线程工作...");throw new RuntimeException("内部错误");} catch (Exception e) {// 在线程内部直接处理异常System.out.println("子线程自己处理了异常: " + e.getMessage());// 可以在这里将异常信息通过共享变量、回调接口等方式传递回主线程}});childThread.start();}
}

总结与对比

方法优点缺点适用场景
UncaughtExceptionHandler集中处理,是Java提供的标准机制,可全局设置。处理器被调用时,异常线程已即将终止,无法恢复。日志记录、全局监控、防止线程异常导致整个应用静默失败。
Future + ExecutorService最强大和推荐。可精确控制、可取消、可获取返回值、异常清晰传递。需要学习线程池API,future.get() 会阻塞主线程。绝大多数生产环境,特别是需要任务结果和异常处理的场景。
内部 try-catch灵活性高,可以针对不同逻辑进行精细处理。代码侵入性强,每个线程都要写,异常信息难以上报。简单的线程任务,异常可以在线程内部自行消化解决的情况。

对于需要从主线程感知子线程异常的场景,优先选择 ExecutorService 和 Future。如果只是想进行最后的日志记录或清理,则使用 UncaughtExceptionHandler。

如何在多个子线程中捕获异常并引发主线程异常?

http://www.xdnf.cn/news/1489123.html

相关文章:

  • 第4篇 conda install pytorch==2.0.0报错
  • Keil快捷键代码补全
  • Photoshop图层间的关系
  • ⚡ 浪涌测试波形全解析:从标准到应用
  • redis的高可用(哨兵)
  • Redis(49)Redis哨兵如何实现故障检测和转移?
  • PicoZed™ SDR Z7035/AD9361
  • Shell 脚本自动安装 Nginx
  • OSPF基础部分知识点
  • Redis基础(含常用命令等以快速入门)
  • 学习结构体
  • 常见的内存泄露情况汇总
  • STM32 开发(三十三)STM32F103 片内资源 —— 直接存储 DMA 实战 编码详解
  • TypeORM 入门教程之 `@OneToOne` 关系详解
  • Day23_【机器学习—集成学习(5)—Boosting—XGBoost算法】
  • Python struct模块 | 使用pack函数进行字节序打包
  • k8s镜像推送到阿里云,使用ctr推送镜像到阿里云
  • Python实战:打造简易人脸识别门禁系统
  • MySQL 主从读写分离架构
  • UserManagement.vue和Profile.vue详细解释
  • Windows 内存整理和优化工具 - Wise Memory Optimize
  • Java初体验
  • 缓存无处不在
  • fps:AI系统
  • 2.TCP深度解析:握手、挥手、状态机、流量与拥塞控制
  • 火山 RTC 引擎15 拉流 推流 地址生成器 、合流转推 开关
  • Vulkan 学习(20)---- UniformBuffer 的使用
  • 【系统分析师】第7章-基础知识:软件工程(核心总结)
  • 计算机毕设选题:基于Python+Django的B站数据分析系统的设计与实现【源码+文档+调试】
  • 阿里云上启动enclave 并与宿主机通信