简介
说明
本文用示例介绍如何全局捕获Java线程池中的异常。
本文内容概述
本文先用示例介绍不捕获异常时的现象(即:“实例:不捕获异常”),再针对线程池的任务提交的三种方式分别说明异常处理的方法。线程池的任务提交的三种方式如下:
- Thread + execute
- 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)
场景1:Thread
注意:下边必须要用execute执行,不要用submit。因为submit发生异常时获取不到Throwable(Throwable是null)。
法1:自定义ThreadPoolExecutor
自定义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<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("捕获到异常。异常信息为:" + t.getMessage());
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.execute(myThread);
executor.shutdown();
System.out.println("主线程结束");
}
}
执行结果
主线程开始 线程1:运行开始 主线程结束 捕获到异常。异常信息为:/ by zero 异常栈信息为: java.lang.ArithmeticException: / by zero at com.example.a.MyThread.run(Demo.java:11) 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.MyThread.run(Demo.java:11) 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)
场景2:Runnable + execute
注意:下边必须要用execute执行,不要用submit。因为submit发生异常时获取不到Throwable(Throwable是null)。
法1:自定义ThreadPoolExecutor
自定义ThreadPoolExecutor, 实现Thread.UncaughtExceptionHandler接口的uncaughtException方法。
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 thread, Throwable t) {
System.out.println("捕获到异常。异常信息为:" + t.getMessage());
System.out.println("异常栈信息为:");
t.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;
}
// 或者这么写
// @Override
// public Thread newThread(Runnable r) {
// Thread thread = new Thread(r);
// thread.setUncaughtExceptionHandler((theThread, theThrowable) -> {
// log.error("机器人线程池抛出异常。线程名:{}", theThread.getName(), theThrowable);
// });
// return thread;
// }
});
exec.execute(new MyThread("线程1"));
exec.shutdown();
System.out.println("主线程结束");
}
}
执行结果
主线程开始 线程1:运行开始 主线程结束 捕获到异常。异常信息为:/ by zero 异常栈信息为: 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)
法2:ThreadFactory
自定义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 thread, Throwable t) {
System.out.println("捕获到异常。异常信息为:" + t.getMessage());
System.out.println("异常栈信息为:");
t.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:运行开始 主线程结束 捕获到异常。异常信息为:/ by zero 异常栈信息为: 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)
法3:默认的handler
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 thread, Throwable t) {
System.out.println("捕获到异常。异常信息为:" + t.getMessage());
System.out.println("异常栈信息为:");
t.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:运行开始 主线程结束 捕获到异常。异常信息为:/ by zero 异常栈信息为: 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)
场景3:Callable + submit
法1:自定义ThreadPoolExecutor
说明
自定义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<Runnable> 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.getMessage());
System.out.println("异常栈信息为:");
t.printStackTrace();
}
} else if (r instanceof FutureTask) {
FutureTask futureTask = (FutureTask) r;
try {
futureTask.get();
} catch (InterruptedException e) {
System.out.println("捕获到InterruptedException异常。异常信息为:" + e.getMessage());
System.out.println("异常栈信息为:");
e.printStackTrace();
} catch (ExecutionException e) {
System.out.println("捕获到ExecutionException异常。异常信息为:" + e.getMessage());
System.out.println("异常栈信息为:");
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.lang.ArithmeticException: / by zero 异常栈信息为: 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:38) 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
原理
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 ,这样如果疏漏了异常捕获,也不至于丢掉异常信息。

请先 !