写点什么

并发王者课 - 青铜 9:防患未然 - 如何处理线程中的异常

用户头像
秦二爷
关注
发布于: 2021 年 06 月 10 日

欢迎来到《并发王者课》,本文是该系列文章中的第 9 篇


在本篇文章中,我将为你介绍线程中异常的处理方式以及 uncaughtExceptionHandler 用法

一、新线程中的异常去哪了

应用程序在执行过程中,难免会出现各种意外错误,如果我们没有对错误进行捕获处理,会直接影响应用的运行结果,甚至导致应用崩溃。而在应用异常处理中,多线程的异常处理是比较重要又容易犯错的地方。


接下来,我们通过一段代码模拟一种常见的多线程异常处理方式。


在下面的代码中,我们在主线程中创建了新线程 nezhaThread,并期望在主线程中捕获新线程中抛出的异常:


 public static void main(String[] args) {        Thread neZhaThread = new Thread() {            public void run() {                throw new RuntimeException("我是哪吒,我被围攻了!");            }        };        // 尝试捕获线程抛出的异常        try {            neZhaThread.start();        } catch (Exception e) {            System.out.println("接收英雄异常:" + e.getMessage());        }    }
复制代码


运行结果如下:


Exception in thread "Thread-0" java.lang.RuntimeException: 我是哪吒,我被围攻了!  at cn.tao.king.juc.execises1.ExceptionDemo$1.run(ExceptionDemo.java:7)
Process finished with exit code 0
复制代码


对于多线程新手来说,可能并不能直接看出其中的不合理。然而,从运行的结果中可以看到,没有输出“接收英雄异常”关键字。也就是说,主线程并未能捕获新线程的异常。 那这是为什么呢?


理解这一现象,首先要从线程的本质出发。在 Java 中,每个线程所运行的都是独立运行的代码片段,如果我们没有主动提供线程间通信和协作的机制,那么它们彼此之间是隔离的。


换句话说,每个线程都要在自己的闭环内完成全部的任务处理,包括对异常的处理,如果出错了但你没有主动处理异常,那么它们会按照既定的流程自我了结


二、多线程中的异常处理方式

1. 从主线程看异常的处理

为了理解多线程中的错误处理方式,我们先看常见的主线程是如何处理错误的,毕竟相对于多线程,单一的主线程更容易让人理解。


 public static void main(String[] args) {        throw new NullPointerException();    }
复制代码


很明显,上面这段代码将会抛出下面错误信息:


Exception in thread "main" java.lang.NullPointerException  at cn.tao.king.juc.execises1.ExceptionDemo.main(ExceptionDemo.java:21)
复制代码


对于类似于空指针错误的堆栈信息,相信你一定并不陌生。在主线程中处理这样的异常很简单,通过编写trycatch代码块即可。但其实,除了这种方式外,我们还可以通过定义uncaughtExceptionHandler来处理主线程中的异常。


 public static void main(String[] args) {        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());        throw new NullPointerException();    }
// 自定义错误处理 static class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.println("出错了!线程名:" + t.getName() + ",错误信息:" + e.getMessage()); } }
复制代码


输出结果如下:


出错了!线程名:main,错误信息:null
Process finished with exit code 1
复制代码


你看,我们已经地捕获了异常。然而,你可能会疑惑为什么 Thread.UncaughtExceptionHandler 可以自定义错误处理?说到这,就不得不提 Java 中的异常处理方式,如下图所示:



在 Java 中,我们经常可以看到空指针那样的错误的堆栈信息,然而这个堆栈实则是线程在出错的情况下 “不得已” 才输出来的。从图中我们可以看到:


  • 当线程出错时,首先会检查当前线程是否指定了错误处理器;

  • 如果当前线程没有指定错误处理器,则继续检查其所在的线程组是否指定(注意,前面我们已经说过,每个线程都是有线程组的);

  • 如果当前线程的线程组也没有指定,则继续检查其父线程是否指定;

  • 如果父线程同样没有指定错误处理器,则最后检查默认处理是否设置;

  • 如果默认处理器也没有设置,那么将不得不输出错误的堆栈信息

2. 多线程间的异常处理

不要忘记,主线程也是线程,所以当你理解了主线程的错误处理方式后,你也就理解了子线程中的异常处理方式,它们和主线程是相同的。在主线程中,我们可以通过Thread.setDefaultUncaughtExceptionHandler来设置自定义异常处理器。而在新的子线程中,则可以通过线程对象直接指定异常处理器,比如我们给前面的 neZhaThread 线程设置异常处理器:


neZhaThread.setName("哪吒");neZhaThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
复制代码


那么,设置处理器后的线程异常信息则输出如下:


出错了!线程名:哪吒,错误信息:我是哪吒,我被围攻了!
Process finished with exit code 0
复制代码


你看,通过定义uncaughtExceptionHandler,我们已经捕获并处理了新线程抛出的异常。

3. 理解 UncaughtExceptionHandler

从上面的代码中,相信你已经直观地理解 UncaughtExceptionHandler 用法。在 Java 中,UncaughtExceptionHandler 用于处理线程突然异常终止的情况。当线程因某种原因抛出未处理的异常时,JVM 虚拟机将会通过线程中的getUncaughtExceptionHandler查询该线程的错误处理器,并将该线程和异常信息作为参数传递过去。如果该线程没有指定错误处理器,将会按照上图所示的流程继续查找。

三、 定义 UncaughtExceptionHandler 的三个层面

1. 定义默认异常处理器

默认的错误处理器可以作为线程异常的兜底处理器,在线程和线程组未指定异常处理器时,可以使用默认的异常处理器。


 Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
复制代码

2. 自定义特定的异常处理器

如果某个线程需要特定的处理器时,通过线程对象指定异常处理器是个很不错的选择。当然,这种异常处理器不可以与其他线程共享。


neZhaThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
复制代码

3. 继承 ThreadGroup

通过继承 ThreadGroup 并覆写uncaughtException可以重设当前线程组的异常处理器逻辑。不过要注意的是,覆写线程组的行为并不常见,使用时需要慎重。


public class MyThreadGroupDemo  extends ThreadGroup{    public MyThreadGroupDemo(String name) {        super(name);    }
@Override public void uncaughtException(Thread t, Throwable e) { // 在这里重写线程组的异常处理逻辑 System.out.println("出错了!线程名:" + t.getName() + ",错误信息:" + e.getMessage()); }}
复制代码

小结

以上就是关于线程异常处理的全部内容,在本文中我们介绍了多线程异常的处理方式以及uncaughtExceptionHandler的用法。对于多线程的异常处理应该记住:


  • 线程内部的异常应尽可能在其内部解决

  • 如果主线程需要捕获子线程异常,不可以使用trycatch,而是要使用uncaughtExceptionHandler。当然,已经在子线程内部捕获的异常,主线程将无法捕获。


正文到此结束,恭喜你又上了一颗星✨


夫子的试炼


  • 编写代码了解并体验 uncaughtExceptionHandler 用法。


延伸阅读



关于作者


关注公众号【庸人技术笑谈】,获取及时文章更新。记录平凡人的技术故事,分享有品质(尽量)的技术文章,偶尔也聊聊生活和理想。不贩卖焦虑,不做标题党。


如果本文对你有帮助,欢迎点赞关注监督,我们一起从青铜到王者

发布于: 2021 年 06 月 10 日阅读数: 9
用户头像

秦二爷

关注

微信公众号:【庸人技术笑谈】 2018.05.13 加入

记录平凡人的技术故事,分享有品质(尽量)的技术文章,偶尔也聊聊生活和理想。不贩卖焦虑,不兜售课程。

评论

发布
暂无评论
并发王者课-青铜9:防患未然-如何处理线程中的异常