简介
说明
本文用示例介绍如何全局捕获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; } }); 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 ,这样如果疏漏了异常捕获,也不至于丢掉异常信息。
请先
!