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的多线程体系。那关于这个体系的异常如何处理呢?笔者会后续博文中介绍!