说明
本文用示例介绍如何全局捕获Java线程池中的异常。
本文内容概述
本文先用示例介绍不捕获异常时的现象(即:“实例:不捕获异常”),再针对线程池的任务提交的三种方式分别说明异常处理的方法。线程池的任务提交的三种方式如下:
Thread
Runnable + execute
Callable + submit
说明
当抛出RuntimeException异常时,线程就会终结,而对于主线程和其他线程完全不受影响,且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常)。
实例
package com.example.a;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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("主线程开始");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new MyThread("线程1"));
executor.shutdown();
System.out.println("主线程结束");
}
}
执行结果
主线程开始
主线程结束
线程1:运行开始
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at com.example.a.MyThread.run(Demo.java:16)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
自定义ThreadPoolExecutor, 重写afterExecute方法
package com.example.a;
import java.util.concurrent.*;
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 MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("捕获到异常。异常栈信息为:");
t.printStackTrace();
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
ExecutorService executor = new MyThreadPoolExecutor(5,50, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
MyThread myThread = new MyThread("线程1");
executor.submit(myThread);
executor.shutdown();
System.out.println("主线程结束");
}
}
执行结果
主线程开始
线程1:运行开始
主线程结束
捕获到异常。异常栈信息为:
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
at com.example.a.MyThreadPoolExecutor.afterExecute(Demo.java:30)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1157)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
自定义ThreadPoolExecutor, 重写afterExecute方法
package com.example.a;
import java.util.concurrent.*;
class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + ":运行开始");
int i = 1 / 0;
System.out.println(name + ":运行结束");
}
}
class MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("捕获到异常。异常栈信息为:");
t.printStackTrace();
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
ExecutorService executor = new MyThreadPoolExecutor(5,50, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
MyRunnable myRunnable = new MyRunnable("线程1");
executor.execute(myRunnable);
executor.shutdown();
System.out.println("主线程结束");
}
}
执行结果
主线程开始
线程1:运行开始
主线程结束
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
at com.example.a.MyRunnable.run(Demo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at com.example.a.MyRunnable.run(Demo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
若改为executor.submit(myRunnable); 结果如下(报空指针错误):
主线程开始
主线程结束
线程1:运行开始
捕获到异常。异常栈信息为:
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
at com.example.a.MyThreadPoolExecutor.afterExecute(Demo.java:30)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1157)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
自定义ThreadFactory, 给线程工厂生产出来的Thread实例设置UncaughtExceptionHandler
package com.example.a;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
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("主线程开始");
ExecutorService exec = Executors.newSingleThreadExecutor(new ThreadFactory(){
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
return thread;
}
});
exec.execute(new MyThread("线程1"));
exec.shutdown();
System.out.println("主线程结束");
}
}
执行结果
主线程开始
线程1:运行开始
主线程结束
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
at com.example.a.MyThread.run(Demo.java:17)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
若改为executor.submit(myRunnable); 结果如下:(捕获不到异常)
主线程开始
主线程结束
线程1:运行开始
package com.example.a;
import java.util.concurrent.*;
class MyRunnable implements Runnable {
private String name;
public MyRunnable(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());
ExecutorService executor = Executors.newSingleThreadExecutor();
MyRunnable myRunnable = new MyRunnable("线程1");
executor.execute(myRunnable);
executor.shutdown();
System.out.println("主线程结束");
}
}
主线程开始
主线程结束
线程1:运行开始
捕获到异常。异常栈信息为:
java.lang.ArithmeticException: / by zero
at com.example.a.MyRunnable.run(Demo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
若改为executor.submit(myRunnable); 结果如下:(捕获不到异常)
主线程开始
线程1:运行开始
主线程结束
说明
自定义ThreadPoolExecutor, 重写afterExecute方法
实例
package com.example.a;
import java.util.concurrent.*;
class MyCallable implements Callable {
private String name;
public MyCallable(String name) {
this.name = name;
}
@Override
public Object call() throws Exception {
System.out.println(name + ":运行开始");
int i = 1 / 0;
return null;
}
}
class MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (r instanceof Thread) {
if (t != null) {
System.out.println("捕获到Thread异常。异常栈信息为:");
t.printStackTrace();
}
} else if (r instanceof FutureTask) {
FutureTask futureTask = (FutureTask) r;
try {
futureTask.get();
} catch (InterruptedException e) {
System.out.println("InterruptedException。异常栈信息为:");
e.printStackTrace();
} catch (ExecutionException e) {
System.out.println("捕获到ExecutionException异常。异常栈信息为:");
e.printStackTrace();
}
}
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("主线程开始");
ExecutorService executor = new MyThreadPoolExecutor(5,50, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
MyCallable myCallable = new MyCallable("线程1");
executor.submit(myCallable);
executor.shutdown();
System.out.println("主线程结束");
}
}
主线程开始
主线程结束
线程1:运行开始
捕获到ExecutionException异常。异常栈信息为:
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.MyThreadPoolExecutor.afterExecute(Demo.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1157)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ArithmeticException: / by zero
at com.example.a.MyCallable.call(Demo.java:15)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
... 2 more
其他网址
线程池异常处理详解,一文搞懂_helloworld-CSDN博客_线程池异常
execute和submit有什么区别?
execute
execute提交的任务,会被封装成一个Runable任务,然后Runable对象被封装成一个Worker,最后在Worker的run方法里面跑runWoker方法, 里面再又调了我们最初的参数 Runable任务的任务,并且用try-catch捕获了异常,会被直接抛出去,因此我们在execute中看到了我们的任务的异常信息。
submit
submit提交的任务,会被封装成一个Runable任务,然后Runable对象被封装成一个FutureTask ,里面的 run 方法 try-catch了所有的异常,并设置到了outcome(Object类型)里面, 可以通过FutureTask .get获取到outcome。
所以在submit提交的时候,里面发生了异常, 是不会有任何抛出信息的。在submit里面,除了从返回结果里面取到异常之外, 没有其他方法了。
因此,在不需要返回结果的情况下,最好用execute ,这样如果疏漏了异常捕获,也不至于丢掉异常信息。