Java 多线程对于异常的处理

概要

本文主要介绍一个类的使用,线程内部类UncaughtExceptionHandler。通过简要分析一下线程对于异常的处理,如checked exception我们可以直接捕获它,而对于unchecked exception也就是平常所说的RuntimeException的处理方式,加深对处理异常的理解。

当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。

Thread.getUncaughtExceptionHandler() 查询该线程以获得其 UncaughtExceptionHandler 的线程,并调用处理程序的 uncaughtException 方法,将线程和异常作为参数传递。如果某一线程没有明确设置其 UncaughtExceptionHandler,则将它的 ThreadGroup 对象作为其 UncaughtExceptionHandler。如果 ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给默认的未捕获异常处理程序。

这就是JDK对UncaughtExceptionHandler的概要描述。应该有人遇到过这样一个问题吧,试图在Runnable或Thread的run方法中抛出throws异常,当然了这种方法是行不通的,因为开发工具直接就会提示编译时错误,但是可以try catch。

thread_exception

JVM的这种设计源自于这样一种理念:“线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉,换句话说,我们不能捕获从线程中逃逸的异常。问题就出在这里,虽然常见的checked exception会被我们try catch掉,但线程仍然有可能抛出unchecked exception, 当此类异常跑抛出时,线程就会终结,而对于主线程和其他线程完全不受影响,且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常)。但是当一个unchecked exception在一个线程的run()方法中抛出,默认的行为是将堆栈跟踪信息写到控制台中(或者记录到错误日志文件中)然后退出程序。

但如果线程确实没有自己try catch某个unchecked exception,而我们又想在线程代码边界之外(run方法之外)来捕获和处理这个异常的话,java为我们提供了一种线程内发生异常时能够在线程代码边界之外处理异常的回调机制,以避免程序终止。我们可以通过UncaughtExceptionHandler来实现这种机制。

让我们来做个UncaughtExceptionHandler的使用示例。在这个例子中,我们已经创建一个线程,在线程中我们简单做一个除法运算,将除数设为0。这时候线程会抛出java.lang.ArithmeticException。当程序不去捕获异常时,异常经过JVM的同时线程也被杀死。这确实属于正常的行为,但不是我们希望看到的。

不使用UncaughtExceptionHandler

我们可以看到控制台输出红色的异常信息:

thread_exception_out

这种信息很显然不是我们所期望的,如果在手机开发中一旦非UI线程抛出unchecked exception,程序马上就会崩溃退出,给用户很糟糕的体验。我们更希望以一种可控的方式将异常信息处理掉。

使用UncaughtExceptionHandler

我们自定义一个类MyExceptionHandler实现接口UncaughtExceptionHandler

thread_handler

仍然在控制台输出了异常信息,但是这个异常信息是我们自己让它输出来的,这时候的异常信息已经不再是默认系统抛出到控制台的那种红色的异常,而是以可控的方式输出的更为详细的信息。在实际开发中我们可以根据方法uncaughtException(Thread t, Throwable e)来区别对待线程中的异常信息,在Android开发中,针对UI线程和非UI线程采用不同的处理方式处理,将异常信息然后发送到服务端,方便在下次版本更新时修复相关unchecked exception。

与线程池结合使用

线程池在多线程开发中是最常见的一种方式,下面我们讨论一下在线程池中如何来捕获异常。

网上有些博客中说道,与线程池结合使用时有时UncaughtExceptionHandler并没有起到我们想要的效果或者说根本没有起作用,就像上面的代码。

而事实上这种使用方式也确实根本不起作用,我们知道UncaughtExceptionHandler是线程的内部类,肯定要与Thread结合使用才有效果,但是这里的几行代码,表面上看上去使用了线程,但是实际上并没有使用。

这两行代码在这里根本就是多余的,exec.execute(task)根本就与t对象一点关系都搭不上。我们在使用线程池创建线程时必须把UncaughtExceptionHandler对象设置到对应的线程中去才能捕获到异常信息。而newCachedThreadPool()在创建是所传入的默认Thread对象中没有UncaughtExceptionHandler的影子。

所以在这里我们需要自己重写ThreadFactory对象然后传入到newCachedThreadPool()

thread_pool_handler

小结

在线程池与UncaughtExceptionHandler的结合使用中需要我们重写ThreadFactory,事实上还有一种更为简单的方式来处理,直接使用Thread的静态方法setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh),该方法是设置所有线程默认的异常处理器。Java线程这是一块很难啃的骨头,开发中常涉及到并发、锁等问题,这一部分自己仍然没有完全搞明白,刚好开发中遇到了针对线程异常处理的一些相关知识点,所以索性就写一篇文章记录一下,当然了限于能力有限,如有错误不当之处,还请及时指正以便查漏补缺共同进步!

本文地址www.sunnyang.com/293.html

参考资料

Java thread中对异常的处理策略

Java 线程中的异常捕获

java多线程中的异常处理

使用UncaughtExceptionHandler重启线程

发表评论