用Thread中的UncaughtExceptionHandler来处理未捕获的异常

举报
程序员小假 发表于 2025/09/19 14:46:22 2025/09/19
【摘要】 Java中在处理异常的时候,通常的做法是使用try-catch-finally来包含代码块,但是Java自身还有一种方式可以处理——使用UncaughtExceptionHandler。它能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器(这是Thread类中的接口)...

Java中在处理异常的时候,通常的做法是使用try-catch-finally来包含代码块,但是Java自身还有一种方式可以处理——使用UncaughtExceptionHandler。它能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器(这是Thread类中的接口):

  1. //Thread类中的接口
  2. public interface UncaughtExceptionHanlder {
  3. void uncaughtException(Thread t, Throwable e);
  4. }

在Java 5以后,可以通过以下实例方法来为每个线程设置一个UncaughtExceptionHandler:

  1. Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler handler);//实例方法

或者通过以下静态方法来设置默认的UncaughtExceptionHandler:

  1. Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler);//静态方法

这些异常处理器中,只有一个将会被调用——JVM首先搜索每个线程的异常处理器,若没有,则搜索该线程的ThreadGroup的异常处理器。ThreadGroup中的默认异常处理器实现是将处理工作逐层委托给上层的ThreadGroup,直到某个ThreadGroup的异常处理器能够处理该异常,否则一直传递到顶层的ThreadGroup。顶层ThreadGroup的异常处理器委托给默认的系统处理器(如果默认的处理器存在,默认情况下为空),否则把栈信息输出到System.err。下面是一个Example:

  1. import java.lang.Thread.UncaughtExceptionHandler;
  2. public class T {
  3. public static void main(String[] args) throws Exception {
  4. //为所有线程设置默认的未捕捉异常处理器
  5. Thread.setDefaultUncaughtExceptionHandler(new MyDefaultExceptionHandler());
  6. Thread.currentThread().setName("Main Thread");
  7. Thread thread = new Thread(new MyTask("MyTask"), "Child Thread");
  8. //为某个线程单独设置异常处理器
  9. thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
  10. thread.start();
  11. //主线程抛出异常,将会使用默认的异常处理器
  12. throw new RuntimeException("IllegalArgumentException");
  13. }
  14. }
  15. class MyDefaultExceptionHandler implements UncaughtExceptionHandler {
  16. @Override
  17. public void uncaughtException(Thread t, Throwable e) {
  18. System.out.println("MyDefaultExceptionHandler: Thread: " +
  19. t.getName() + ", Message: " + e.getMessage());
  20. }
  21. }
  22. class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
  23. @Override
  24. public void uncaughtException(Thread t, Throwable e) {
  25. System.out.println("MyUncaughtExceptionHandler: Thread: " +
  26. t.getName() + ", Message: " + e.getMessage());
  27. }
  28. }
  29. class MyTask implements Runnable {
  30. private String name;
  31. public MyTask(String name) {
  32. this.name = name;
  33. }
  34. public MyTask(){}
  35. public String getName() {
  36. return name;
  37. }
  38. @Override
  39. public void run() {
  40. throw new RuntimeException(name + " gets a NullPointerException");
  41. }
  42. }

执行结果:

  1. MyDefaultExceptionHandler: Thread: Main Thread, Message: IllegalArgumentException
  2. MyUncaughtExceptionHandler: Thread: Child Thread, Message: MyTask gets a NullPointerException

可以看到,Main Thread由于没有显式设置UncaughtExceptionHandler,其抛出的未捕获异常,被默认异常处理器MyDefaultUncaughtExceptionHandler处理了,而Child Thread由于单独设置了UncaughtExceptionHanlder,其抛出的未捕获异常,则被Thread本身自带的MyUncaughtExceptionHandler处理了。
如果要为线程池中的所有线程设置一个UncaughtExceptionHandler,则需要为ThreadPoolExecutor的构造函数提供一个自定义ThreadFactory(与所有的线程操控一样,只有线程的所有者能够改变线程的UncaughtExceptionHandler):

  1. import java.lang.Thread.UncaughtExceptionHandler;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.ThreadFactory;
  5. public class T {
  6. public static void main(String[] args) throws Exception {
  7. //使用自定义的ThreadFactory来创建线程,并绑定同一个异常处理器
  8. UncaughtExceptionHandler handler = new MyUncaughtExceptionHandler();
  9. ExecutorService executor = Executors.newCachedThreadPool(new MyThreadFactory(handler));
  10. executor.execute(new MyTask("task1"));
  11. executor.execute(new MyTask("task2"));
  12. executor.execute(new MyTask("task3"));
  13. executor.shutdown();
  14. }
  15. }
  16. class MyTask implements Runnable {
  17. private String name;
  18. public MyTask(String name) {
  19. this.name = name;
  20. }
  21. public MyTask(){}
  22. public String getName() {
  23. return name;
  24. }
  25. @Override
  26. public void run() {
  27. throw new RuntimeException(name + " gets a NullPointerException");
  28. }
  29. }
  30. class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
  31. @Override
  32. public void uncaughtException(Thread t, Throwable e) {
  33. System.out.println("MyUncaughtExceptionHandler: Thread: " +
  34. t.getName() + ", Message: " + e.getMessage());
  35. }
  36. }
  37. class MyThreadFactory implements ThreadFactory {
  38. private UncaughtExceptionHandler handler;
  39. public MyThreadFactory(UncaughtExceptionHandler handler) {
  40. this.handler = handler;
  41. }
  42. @Override
  43. public Thread newThread(Runnable r) {
  44. Thread thread = new Thread(r);
  45. //在这里设置异常处理器
  46. thread.setUncaughtExceptionHandler(handler);
  47. return thread;
  48. }
  49. }

执行结果:

  1. MyUncaughtExceptionHandler: Thread: Thread-0, Message: task1 gets a NullPointerException
  2. MyUncaughtExceptionHandler: Thread: Thread-2, Message: task3 gets a NullPointerException
  3. MyUncaughtExceptionHandler: Thread: Thread-1, Message: task2 gets a NullPointerException

从结果中可以看出,线程池中的每个线程都使用同一个异常处理器来处理未捕获的异常。
不过,上面的结果能证明:通过**execute**提交的任务,能将它抛出的异常交给未捕获的异常处理器。下面的例子只修改了main方法(其余部分请参考前文),以submit方式提交任务:

  1. public class T {
  2. public static void main(String[] args) throws Exception {
  3. //使用自定义的ThreadFactory来创建线程,并绑定同一个异常处理器
  4. UncaughtExceptionHandler handler = new MyUncaughtExceptionHandler();
  5. ExecutorService executor = Executors.newCachedThreadPool(new MyThreadFactory(handler));
  6. //通过submit方法提交任务
  7. Future future1 = executor.submit(new MyTask("task1"));
  8. Future future2 = executor.submit(new MyTask("task2"));
  9. System.out.println(future1.get());
  10. System.out.println(future2.get());
  11. executor.shutdown();
  12. }
  13. }

执行结果:

  1. Exception in thread "main" java.util.concurrent.ExecutionException:
  2. java.lang.RuntimeException: task1 gets a NullPointerException
  3. at java.util.concurrent.FutureTask.report(FutureTask.java:122)
  4. at java.util.concurrent.FutureTask.get(FutureTask.java:188)
  5. at T.main(T.java:15)
  6. Caused by: java.lang.RuntimeException: task1 gets a NullPointerException
  7. at MyTask.run(T.java:31)
  8. at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
  9. at java.util.concurrent.FutureTask.run(FutureTask.java:262)
  10. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
  11. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
  12. at java.lang.Thread.run(Thread.java:745)

结果说明,future1.get的时候遇到了ExecutionException。再来看看Future.get方法的实现(在java.util.concurrent.FutureTask类中):

  1. public V get() throws InterruptedException, ExecutionException {
  2. int s = state;
  3. if (s <= COMPLETING)//如果任务没有结束,则等待结束
  4. s = awaitDone(false, 0L);
  5. return report(s);//如果执行结束,则报告执行结果
  6. }
  7. @SuppressWarnings("unchecked")
  8. private V report(int s) throws ExecutionException {
  9. Object x = outcome;
  10. if (s == NORMAL)//如果执行正常,则返回结果
  11. return (V)x;
  12. if (s >= CANCELLED)//如果任务被取消,调用get则报CancellationException
  13. throw new CancellationException();
  14. throw new ExecutionException((Throwable)x);//执行异常,则抛出ExecutionException
  15. }

源代码说明:如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。所以,通过**submit**提交到线程池的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分,因此不会交由异常处理器来处理。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。