简介
说明
本文用示例介绍如何全局捕获处理Java多线程中的异常。
在Java中,线程中的异常是不能抛出到调用该线程的外部方法中捕获的,只能在线程内部进行捕获。
为什么不能抛出异常到外部线程捕获?
因为线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。基于这样的设计理念,在Java中,线程方法的异常都应该在线程代码边界之内(run方法内)进行处理。换句话说,我们不能捕获从线程中逃逸的异常。
JDK如何控制线程异常不会跑到外部线程?
通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束。
如果在线程中抛出异常线程会怎么样?
线程会立即终结。
如何捕获线程中的异常?
法1:使用try-catch-finally来包含代码块
法2:使用全局异常处理 UncaughtExceptionHandler
UncaughtExceptionHandler能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器(这是Thread类中的接口):
public interface UncaughtExceptionHanlder {
void uncaughtException(Thread t, Throwable e);
}
JDK5之后可以在每一个Thread对象上添加一个异常处理器UncaughtExceptionHandler 。Thread.UncaughtExceptionHandler.uncaughtException()方法会在线程因未捕获的异常而面临死亡时被调用。
实例:不捕获异常
package com.example.a;
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + ":运行开始");
int i = 1 / 0;
System.out.println(name + ":运行结束");
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
Thread thread = new Thread(new MyThread("线程1"));
thread.start();
System.out.println("主线程结束");
}
}
执行结果
主线程开始
主线程结束
线程1:运行开始
Exception in thread "Thread-1" java.lang.ArithmeticException: / by zero
at com.example.a.MyThread.run(Demo.java:13)
at java.lang.Thread.run(Thread.java:748)
全局异常处理
本处只展示全局异常处理的方案。在线程代码里边加try catch这种方法就不贴出了,很基础。
法1,2,3也同样适用于Runnable方式创建的线程,比如:Thread thread = new Thread(new MyRunnable("线程1"));
法1:handler
package com.example.a;
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + ":运行开始");
int i = 1 / 0;
System.out.println(name + ":运行结束");
}
}
class MyUncheckedExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获到异常。异常栈信息为:");
e.printStackTrace();
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
Thread thread = new Thread(new MyThread("线程1"));
thread.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
thread.start();
System.out.println("主线程结束");
}
}
执行结果
主线程开始
主线程结束
线程1:运行开始
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
at com.example.a.MyThread.run(Demo.java:13)
at java.lang.Thread.run(Thread.java:748)
法2:线程组
package com.example.a;
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获到异常。异常栈信息为:");
e.printStackTrace();
}
};
Thread thread = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
System.out.println("子线程运行开始");
int i = 1 / 0;
System.out.println("子线程运行结束");
}
});
thread.start();
System.out.println("主线程结束");
}
}
运行结果
主线程开始
主线程结束
子线程运行开始
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
at com.example.a.Demo$2.run(Demo.java:19)
at java.lang.Thread.run(Thread.java:748)
法3:默认的handler
如果只需要一个线程异常处理器处理线程的异常,那么我们可以设置一个默认的线程异常处理器,当线程出现异常时,
如果没有指定线程的异常处理器,而且线程组也没有设置,那么就会使用默认的线程异常处理器
package com.example.a;
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + ":运行开始");
int i = 1 / 0;
System.out.println(name + ":运行结束");
}
}
class MyUncheckedExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获到异常。异常栈信息为:");
e.printStackTrace();
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
Thread thread = new Thread(new MyThread("线程1"));
thread.start();
System.out.println("主线程结束");
}
}
执行结果
主线程开始
主线程结束
线程1:运行开始
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
at com.example.a.MyThread.run(Demo.java:13)
at java.lang.Thread.run(Thread.java:748)
法4:FutureTask
上面说的3种方法都是基于线程异常处理器实现的,本方法不需要线程异常处理器。
package com.example.a;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
//1.创建FeatureTask
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("子线程运行开始");
return 1 / 0;
}
});
//2.创建Thread
Thread thread = new Thread(futureTask);
//3.启动线程
thread.start();
try {
Integer result = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
//4.处理捕获的线程异常
System.out.println("捕获到异常。异常栈信息为:");
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
执行结果
主线程开始
子线程运行开始
捕获到异常。异常栈信息为:
主线程结束
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.example.a.Demo.main(Demo.java:27)
Caused by: java.lang.ArithmeticException: / by zero
at com.example.a.Demo$1.call(Demo.java:16)
at com.example.a.Demo$1.call(Demo.java:12)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:748)