Java中线程的异常处理

by:leotse

异常处理

异常,在程序出现的非正常状态;异常处理机制,就是程序语言针对这些异常状态采取的处理措施。当程序出现异常的时候,程序会暂停运行,并且程序的控制权会转交给异常处理器。

在Java中,我们有一套比较完善的异常处理机制。下面是Java中异常的一个示例:

1
2
3
4
5
6
7
8
9
try{
// your code
} catch (Exception1 exception1){
// do something when Exception1 throws
} catch (Exception2 exception2){
// do something when Exception2 throws
} finally {
// do this no matter what happened
}

异常主要分类

在Java中(Android一样),异常的基类是Throwable,而我们平时所看到的Error以及Exception都继承自Throwable


java-exception

Error指的是Java在运行时系统中的内部错误或者资源不够等情况,问题一般较严重;对于Error,程序除了尽快全身而退外,其他什么都做不了。程序也不应该抛出Error对象,它一般是由JVM抛出;
相较于ErrorException就温顺多了,我们一般情况下可以在代码中预测Exception的发生并对症下药,提前编写好异常处理的代码。

Exception包括运行时异常(RuntimeException)以及非运行时异常,运行时异常主要包括类型转换异常、数组越界、空指针等;

运行时异常,都是程序员的错。

非运行时异常,又叫编译异常,一般是外部错误,这些并不是程序本身的问题,而是在环境外部出现的异常,最常见的就是IOException,当程序想要读取或者写入一个不存在文件时,就会抛出IOException。从程序语法角度讲非运行时异常是必须处理的异常,否则程序就不能编译通过

运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,程序中可以选择catch处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

ErrorRuntimeException及其子类统称为unchecked异常,而其他称为checked异常(要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过)。Exception能被程序本身可以处理,Error是无法处理。


java-exception

异常处理流程

Java中,异常的主要处理流程如下:
1.代码块遇到异常,当前代码块停止运行,程序也随之暂停运行,程序控制权转移给异常处理器;
2.异常处理器开始搜索有没有能够处理当前异常的处理器,如果有,则执行处理器中的代码;如果没有,则抛出异常给当前代码块的调用方;
3.接收到抛出异常的代码块会重新经历以上1,2的步骤,如果没有合适的处理器,继续往上层抛出异常;
4.如果异常抛到程序入口仍然没有处理,则将异常抛给JVM;


exception-flow

线程的异常处理

1.线程可以抛出异常
这是一个基本事实,我们可以在线程中抛出一个异常,当前线程会中断,如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class MyThread {
public static void main(String[] args) {
new Thread(new Runnable() {

@Override
public void run(){
System.out.println("Sub thread is running...");
throw new NullPointerException();
}
}).start();
}
}

运行结果:

1
2
3
4
5
Sub thread is running...
Exception in thread "Thread-0"
java.lang.NullPointerException
at com.leotse.thread.MyThread$1.run(MyThread.java:10)
at java.lang.Thread.run(Thread.java:745)

但是,我们不能直接在Override的run方法声明时直接抛出异常,如public void run() throws Exception{...},Java会提示你Exception is not compatible with throws clause in Runnable.run()

2.主线程能否捕捉子线程的异常
首先,我们需要明确一点,线程的run()方法定义了子线程的边界,也就是说,一个线程中的逻辑再怎么运行也不能超越其run()方法。这就明确了

线程代码不能抛出任何checked异常,所有的线程中的checked异常都只能被线程本身消化掉。线程本身就是被看作独立的执行片断,它应该对自己负责,所以由它来消化所有的checked异常是很正常的。

上面说的一直是checked异常,那么unchecked异常是不是不一样?对于unchecked异常中的Error我们显然无能为力,但是对于另外一部分-RuntimeException我们能否处理?我们看下面一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyThread {
public static void main(String[] args) {

try{
new Thread(new Runnable() {

@Override
public void run(){
System.out.println("Sub thread is running...");
throw new RuntimeException();
}
}).start();
} catch (RuntimeException e){
System.out.println("Sub thread throws an exception...");
}

System.out.println("Main thread is running...");

}

}

如果Main thread能捕捉到子线程的运行时异常(前面讲过,属于unchecked异常),那么Sub thread throws an exception...就会被打印出来(即能被catch住),运行结果如下:

1
2
3
4
5
Sub thread is running...Exception in thread "Thread-0" 
Main thread is running...
java.lang.RuntimeException
at com.leotse.thread.MyThread$1.run(MyThread.java:12)
at java.lang.Thread.run(Thread.java:745)

结果显示有点乱,但是我们仍然可以看到,Sub thread throws an exception...这句话并没有打印出来,因此说明在这里我们的MainThread并没有捕捉到子线程抛出的unchecked异常。

线程方法的异常只能自己来处理。

更多关于异常的知识:
Java 异常处理的误区和经验总结