Java入门的多线程方案就是Thread类和Runnable接口。如下:
public class Demo {
public static void main(String[] args) {
fun1();
fun2();
}
public static void fun1() {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("fun1-thread");
}
};
thread.start();
}
public static void fun2() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("fun2-thread");
}
}).start();
}
}
今天我们要说的是线程内部的异常处理。Java的异常(包括Exception和Error)分为检查异常(checked exceptions)和非检查的异常(unchecked exceptions),很好理解:
- 检查异常:编译器要求必须处理的异常,代码在运行前,编译器要求你必须要对这段代码try...catch,或者throws exception,这类这就是检查异常。
- 非检查异常:只有在运行期才能检查出来的异常,比如NullpointException...
由于在多线程中,run()方法无法继续向上显式抛出异常,所以在线程内部的异常我们必须处理,否则异常会被JVM捕获到,这时JVM就会将线程杀掉。线程内部该如何处理异常呢?
- 对于受检异常:就是直接在run()方法体中try-catch异常,然后进行对应的处理;
- 对于非受检异常:虽然编译期不做检查,但仍然可以使用try-catch处理;如果没有try-catch,JVM会帮助我们捕获到异常。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread start...");
try {
int a = 1/0;//uncheck exception
} catch (Exception e) {
System.out.println("uncheck exception...");
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep 10 ms,thread end...");
}
}).start();
//输出:
thread start...
uncheck exception...
after sleep 10 ms,thread end...
上面代码int a = 1/0 是运行期异常,如果用try-catch处理,后面的代码还可以执行,否则抛出异常被JVM捕获,后面的sleep代码就无法执行到。
我们的代码中不可能对所有的运行时异常(unchecked exception)做处理,所以线程中如果未处理的unchecked exception被抛出,那么我们如何处理JVM捕获的异常呢?答案是Thread.UncaughtExceptionHandler类。正如JDK文档所介绍的一样:
“当一个线程由于发生了非受检异常而终止时,JVM会使用Thread.gerUncaughtExceptionHandler()方法查看该线程上的UncaughtExceptionHandler,并调用他的uncaughtException()方法”。
下面我们来尝试使用hread.UncaughtExceptionHandler类,来处理线程内部的非受检异常(受检异常直接在run()方法体内部的catch子句中处理)。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo {
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
//设置非受检异常的ExceptionHandler
Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());
System.out.println("thread start...");
int a = 1/0;//unchecked exception
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep 10 ms,thread end...");
}
}).start();
}
private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("One uncaught exception was got:");
System.out.println("Thread id=" + t.getId());
System.out.println("Thread name=" + t.getName());
e.printStackTrace(System.out);
}
}
}
//输出
thread start...
One uncaught exception was got:
Thread id=10
Thread name=Thread-0
java.lang.ArithmeticException: / by zero
at com.iqiyi.toutiao.Test.ThreadTest$2.run(ThreadTest.java:92)
at java.lang.Thread.run(Thread.java:748)
在线程代码中如果是受检异常,直接在run()方法体中处理;对于非受检异常,我们在run()方法体的开头设置了非受检异常处理类ThreadExceptionException,这个类的uncaughtException()方法就是处理线程内部非捕获异常的具体执行者。(读者要清楚,一旦一个线程抛出了非受检异常,JVM就会把它杀死,然后把捕获到的非受检异常传递给UncaughtExceptionHandler类对象类处理)。
一定有读者会问,如果没有通过setUncaughtExceptionHandler()方法设置Handler怎么办?这个问题在JDK API中给了回答:
“如果一个线程没有显式的设置它的UncaughtExceptionHandler,JVM就会检查该线程所在的线程组是否设置了UncaughtExceptionHandler,如果已经设置,就是用该UncaughtExceptionHandler;否则查看是否在Thread层面通过静态方法setDefaultUncaughtExceptionHandler()设置了UncaughtExceptionHandler,如果已经设置就是用该UncaughtExceptionHandler;如果上述都没有找到,JVM会在对应的console中打印异常的堆栈信息。”
/**
* Created by yizhen on 2017/1/7.
*/
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("static thread exception handler -- " + t.getName());
}
});
final Thread t1 = new Thread(new ThreadTaskWithHandler(), "t1");
t1.start();
final Thread t2 = new Thread(new ThreadTaskNoHandler(), "t2");
t2.start();
}
private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("explicit exception handler -- " + t.getName());
}
}
private static final class ThreadTaskWithHandler implements Runnable {
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());
System.out.println(12 / 0);
}
}
private static final class ThreadTaskNoHandler implements Runnable {
@Override
public void run() {
System.out.println(12 / 0);
}
}
}
从上面的程序可以看到,t1线程的非受检异常始终会被explicit exception handler捕获到,而t2线程的非受检异常始终会被static thread exception handler捕获到。
至此,Thread线程内的异常处理就介绍完了,这包括受检异常和非受检异常。细心的读者会发现,本文仅仅涉及Thread和Runnable的多线程体系。在Java中,还有Executor和Callable的多线程体系。那关于这个体系的异常如何处理呢?笔者会后续博文中介绍!