Java并发编程 - 线程异常处理
《Java编程思想》在讲到异常处理的时候有这样的一段话:
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception型异常。所以Java程序员关心的基本类型通常是Exception。
从上面这段话可以看到,我们在写代码的时候基本上只用关心Exception。
Exception再细分可分为受检查异常(Checked Exception)和不受检查异常(Unchecked Exception)。
受检查异常是在我们写代码的时候必须处理的,不然编译就无法通过,现代的IDE都会检测并提示给我们。比如说在读写文件的时候可能会出现文件不存在这种情况,那么就要对这种情况进行处理,Java对读的文件可能不存在的这种情况,是通过强制我们处理FileNotFoundException异常来完成的。在我们代码中需要捕获这个异常然后做相关处理。
不受检查异常通常也称作运行时异常,Java中所有的这种异常都继承自RuntimeException类,运行型异常顾名思义就是运行时才会抛出的异常。这种异常是无法预先知晓的,只有运行时候才会检测出来,比如说很常见的NullPointerException异常,它是由于我们处理的对象为null导致的。对象是否真正为null,只有运行时才能确定。
我们自己写线程的时候,同样也就涉及到异常的处理,下面来看看关于线程异常处理的问题。
Java中线程通过Thread类来表示,而真正任务的执行l逻辑在Runnable接口的run方法中定义,来看一下Runnable接口的定义:
java.lang.Runnable
public interface Runnable {
public abstract void run();
}
实现Runnable来定义任务执行逻辑,如下:
public class Task implements Runnable {
@Override
public void run() {
// do something
}
}
启动一个线程执行任务,如下:
public static void main(String[] args) {
Task task = new Task();
Thread t1 = new Thread(task);
t.start();
}
t1线程通过主线程启动,然后执行任务。
现在我们回到我们本篇文章要讨论的内容:线程异常处理。也就是处理线程执行的异常,而线程真正的执行逻辑实际上就是Runnable接口中定义的run方法的执行,这个方法执行可能会出现异常,我们要对它进行处理。
上面我们说过代码的执行可能会出现两种异常:受检查异常和不受检查异常。那么在线程运行的时候如何处理呢?
受检查异常处理
受检查异常有两种处理方式:通过try...catch...捕获处理或者通过throw抛出有上层代码处理。
try...catch...处理
@Override
public void run() {
try {
// Code where an exception will occur
} catch (Exception ex) {
// do something
}
}
throw抛出
这种方式不适用于受检查异常的处理,因为Runnable接口定义的run方法不能抛出受检查异常。
在java.lang.Callable接口中,在比较Callable和Runnable的不同的时候有这样的说明:
A {@code Runnable}, however, does not return a result and cannot throw a checked exception.
疑问:这里只说了不能抛出受检查异常,那么是否能抛出不受检查异常给当前线程的启动线程呢?后面作说明。
不受检查异常处理
关于不受检查异常,《Java编程思想》中有这样的一段描述:
属于运行时异常的类型有很多,它们会自动被Java虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从RuntimeException类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出RuntimeException类型的异常(或者任何从RuntimeException继承的异常),它们被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查RuntimeException的话,代码就显得太混乱了。不过尽管通常不用捕获RuntimeException异常,但还是可以在代码中抛出RuntimeException类型的异常。
的确如上述代码所说,有时候我们确实会在代码中抛出RuntimeException异常,作为与其他类或方法的一种交流方式。那么如此所说,在Runnable的run方法中我们也可以这样操作:
@Override
public void run() {
throw new RuntimeException(“我抛出了一个运行时异常!”);
}
当你选择在代码中主动抛出RuntimeException的时候,这时候你的目的我想是为了把当前线程执行的异常抛出交给上层代码处理吧!那么现在我们的问题就来了,上层代码能捕获到你抛出的这个RuntimeException吗?看下面的测试代码:
/**
* @Author rocky.hu
* @Date 10/17/2019 10:33 AM
*/
public class Task implements Runnable {
@Override
public void run() {
throw new RuntimeException("我抛出了一个运行时异常A!");
}
public static void main(String[] args) {
try {
Task task = new Task();
Thread t = new Thread(task);
t.start();
System.out.println("你好啊!");
} catch (RuntimeException e) {
System.out.println("Main捕获到A了。");
}
}
}
运行代码,结果如下:
你好啊!
Exception in thread "Thread-0" java.lang.RuntimeException: 我抛出了一个运行时异常A!
at org.wizard.etcd.Task.run(Task.java:14)
at java.lang.Thread.run(Thread.java:748)
从代码可以看出,main主线程并没有捕获线程t执行时抛出的RuntimeException异常。也就是说通过throw RuntimeException以交给当前线程启动线程处理的这种方式是无效的。
上面是我们主动抛出RuntimeException,同样的Java虚拟机自动抛出的RuntimeException也是无法在上层空间被捕获到。
public class Task implements Runnable {
@Override
public void run() {
String s = null;
s.length();
}
public static void main(String[] args) {
try {
Task task = new Task();
Thread t = new Thread(task);
t.start();
System.out.println("你好啊!");
} catch (RuntimeException e) {
System.out.println("Main捕获到运行时异常。");
}
}
}
运行代码,结果如下:
你好啊!
Exception in thread "Thread-0" java.lang.NullPointerException
at org.wizard.etcd.Task.run(Task.java:13)
at java.lang.Thread.run(Thread.java:748)
那么如果们确定是想捕获线程的运行时异常,应该怎么办?JDK1.5提供了一个接口:java.lang.Thread.UncaughtExceptionHandler。通过这个接口来处理线程的运行时异常,这个从它的名称也可以看出来它的作用。此接口的定义如下:
java.lang.Thread.UncaughtExceptionHandler
@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
使用示例如下:
/**
* @Author rocky.hu
* @Date 10/17/2019 10:33 AM
*/
public class Task implements Runnable {
@Override
public void run() {
throw new RuntimeException("我抛出一个运行时异常!");
}
public static void main(String[] args) {
try {
Task task = new Task();
Thread t = new Thread(task);
t.setUncaughtExceptionHandler(new MyUnCaughtExceptionHandler());
t.start();
System.out.println("你好啊!");
} catch (RuntimeException e) {
System.out.println("Main捕获到运行时异常。");
}
}
}
class MyUnCaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("不受检查异常捕获器:" + e);
}
}
运行结果如下:
你好啊!
不受检查异常捕获器:java.lang.RuntimeException: 我抛出一个运行时异常!