Java 异常传播机制
Java 异常传播机制
- Java 的异常传播处理的机制是基于线程的,它规定了异常产生后如何在方法调用栈中进行传递以及处理,当启动一个新线程时,这个线程会在自己的栈空间中执行任务
- 方法调用栈,也称为调用栈、执行栈或栈帧栈,遵循 LIFO 后进先出(Last In, First Out)的原则进行操作,是 JVM 虚拟机为每个线程分配的一个私有的内存区域(所以线程的栈空间都是相互隔离的),用于存储线程执行时的局部变量、操作数栈、动态链接和方法返回地址等信息(线程的执行环境)
- 在 Java 中通常使用 try-catch 语句块进行异常捕获处理,它可以用来捕获和处理同步代码块中的异常
- 通常需要在每个线程内部自行进行异常捕获处理(比如使用 try-catch 语句块),或者使用 Thread.UncaughtExceptionHandler 来处理线程未捕获的异常
线程的独立性
- Java 线程作为系统中独立调度和执行的基本单位(最小单元),有着自己独立的执行路径(线程在程序中所经历的代码执行顺序,通常取决于程序自身逻辑、线程调度策略、线程间同步、线程间通信、异常处理和线程终止等)和方法调用栈(通过栈帧的创建、压入和弹出等操作来管理方法的调用关系和执行顺序,同时与异常传播机制紧密相连)
异常传播
- 如果一个线程抛出了一个异常且没有在该线程内部被捕获处理时,那么这个异常会导致这个线程终止(对于子线程而言,未捕获的异常会使线程终止,但不影响主线程的运行,而对于主线程而言通常会额外地导致 JVM 虚拟机终止整个程序并打印出异常堆栈信息)而不会被其他线程的调用栈进行处理,异常只能在这个线程的调用栈中传播,无法传播到这个线程的外部(比如这个异常不会自动传播到创建这个线程的父线程或者任何其他线程中,所以线程外部的 try-catch 语句块是无法直接捕获线程内部抛出的异常的)
- 这么设计的目的就是为了隔离不同线程之间的错误,防止一个线程中的错误影响到整个程序的稳定性,正是由于这种隔离性决定了一个线程是无法直接感知到另一个线程中的异常情况的,确保程序在多线程场景下的稳定性和可靠性
- 当方法内部出现异常且没有在该方法内部被捕获处理时,这个异常会沿着方法的调用栈向上传播,直到被 try-catch 语句块捕获或者一直传播直到到达程序的最顶层
异常匹配和捕获
- 在异常沿着调用栈向上传播的过程中,每一层的方法中如果有 try-catch 语句块,就会检查 catch 子句中声明的异常类型是否与传播上来的异常类型匹配,当类型匹配一致时就可以捕获该异常(比如 catch 子句声明的是 Exception,那么就可以捕获 RuntimeException 或 IOException 等异常,因为 Exception 是他们的父类)
捕获异常并重新抛出
- 直接抛出原始异常:直接在 catch 子句中使用 throw 关键字将捕获到的异常再次抛出,通常可以在捕获异常后执行一些清理资源或记录日志等操作,同时将异常重新抛出以便进一步处理
- 抛出包装后的异常:可以将原始异常包装在一个新的异常中,可以提供额外的信息,以便更好、更具体地反映问题的本质
- 抛出自定义的异常:可以在捕获到异常时抛出一个自定义异常,以便转换为更符合业务逻辑、更通用的异常类型,便于在业务代码层面统一处理