你处理过多线程中的异常吗?如何捕获多线程中发生的异常?捕获子线程的异常与捕获当前线程的异常一样简单吗?

除了try catch。Java中还可以通过异常处理器UncaughtExceptionHandler来处理那些未捕获的异常。

# 在当前线程捕获当前线程发生的异常:

/*** @author futao* @date 2020/6/17*/
@Slf4j
public class ExceptionInCurThread {public static void main(String[] args) {try {throw new RuntimeException("在主线程抛出异常,在主线程捕获");} catch (RuntimeException e) {log.error("捕获到异常", e);}}
}
  • 结果:

  • 结论:在当前线程通过try catch可以捕获当前线程抛出的异常。

# 可以在当前通过try catch的方式捕获其他线程抛出的异常吗?'

/*** @author 喜欢天文的pony站长* Created on 2020/6/16.*/
public class ExceptionInChildThread implements Runnable {@Overridepublic void run() {throw new RuntimeException("子线程发生了异常...");}/*** 模拟子线程发生异常** @throws InterruptedException*/private static void exceptionThread() throws InterruptedException {new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}/*** 在主线程尝试通过try catch捕获异常*/private static void catchInMain() {try {exceptionThread();} catch (Exception e) {//无法捕获发生在其他线程中的异常log.error("捕获到了异常?", e);}}public static void main(String[] args) throws InterruptedException {ExceptionInChildThread.catchInMain();}}
  • (错误的)预期:

    • 在运行第一个线程的时候发生了异常,被catch捕获,打印捕获到了异常?和异常堆栈且后面的线程将不会运行。

  • 实际运行结果:

    • 并不符合预期。

    • 没有被try catch捕获。

    • 后续的线程没有因为第一个线程发生异常而跳过。

  • 结论:

    • 无法在一个线程中通过try catch捕获另外一个线程的异常。

# 解决方案

  1. 在每个线程内部run()方法内通过try catch捕获当前线程发生的异常。

    • 缺点:每个线程都需要编写重复的try catch 代码

  2. 使用线程异常处理器UncaughtExceptionHandler

    • 给所有线程设置统一的异常处理器

    • 给每个线程设置特定的异常处理器

    • 给线程组设置异常处理器

    • 给线程池设置异常处理器

      • 因为线程池也是通过new Thread()的方式创建的线程,所以思想与上面两种方法一致。

      • 注意:execute()submit()方式对    异常处理的不同。

# 在线程内部run()通过try catch捕获异常

/*** @author 喜欢天文的pony站长* Created on 2020/6/16.*/
@Slf4j
public class ExceptionInChildThread implements Runnable {@Overridepublic void run() {try {//do something else...throw new RuntimeException("子线程发生了异常...");} catch (Exception e) {log.error("在线程内部捕获异常", e);}}/*** 模拟子线程发生异常** @throws InterruptedException*/private static void exceptionThread() throws InterruptedException {new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}/*** 在主线程尝试通过try catch捕获异常*/private static void catchInMain() {try {exceptionThread();} catch (Exception e) {//无法捕获发生在其他线程中的异常log.error("捕获到了异常?", e);}}public static void main(String[] args) throws InterruptedException {ExceptionInChildThread.catchInMain();}
}
  • 结果:

    • 成功在子线程内部run()方法捕获到了异常

# 使用线程异常处理器UncaughtExceptionHandler

当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器

  • 自定义线程异常处理器

/*** 自定义线程未捕获异常处理器** @author futao* @date 2020/6/17*/
public class CustomThreadUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {private static final Logger LOGGER = LoggerFactory.getLogger(CustomThreadUncaughtExceptionHandler.class);@Overridepublic void uncaughtException(Thread t, Throwable e) {LOGGER.error("捕获到线程发生的异常,线程信息:[{}]", JSON.toJSONString(t), e);}
}
  • 使用:

1. 全局:

  • Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());

    • 通过调用Thread的静态方法setDefaultUncaughtExceptionHandler(),设置Thread的静态属性defaultUncaughtExceptionHandler.为我们自定义的异常处理器。

    • 源码:

测试:

/*** @author 喜欢天文的pony站长* Created on 2020/6/16.*/
@Slf4j
public class ExceptionInChildThread implements Runnable {@Overridepublic void run() {throw new RuntimeException("子线程发生了异常...");}/*** 模拟子线程发生异常** @throws InterruptedException*/private static void exceptionThread() throws InterruptedException {new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}public static void main(String[] args) throws InterruptedException {//设置全局的线程异常处理器Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());exceptionThread();}
}
  • 结果: 成功捕获

2. 为指定线程设置特定的异常处理器

  • 细心的同学已经发现了,在上面Thread类的截图中,还有一个实例属性private volatile UncaughtExceptionHandler uncaughtExceptionHandler;。通过给这个属性赋值,可以实现为每个线程对象设置不同的异常处理器。

  • 测试使用

/*** @author 喜欢天文的pony站长* Created on 2020/6/16.*/
@Slf4j
public class ExceptionInChildThread implements Runnable {@Overridepublic void run() {throw new RuntimeException("子线程发生了异常...");}/*** 模拟子线程发生异常** @throws InterruptedException*/private static void exceptionThread() throws InterruptedException {Thread thread1 = new Thread(new ExceptionInChildThread());//为指定线程设置特定的异常处理器thread1.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());thread1.start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new Thread(new ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}public static void main(String[] args) throws InterruptedException {exceptionThread();}
}
  • 结果: 成功捕获线程1的异常信息

3. 线程组

/*** @author futao* @date 2020/6/20*/
@Slf4j
public class ExceptionInThreadGroup implements Runnable {@Overridepublic void run() {throw new RuntimeException("线程任务发生了异常");}public static void main(String[] args) throws InterruptedException {ThreadGroup threadGroup = new ThreadGroup("只知道抛出异常的线程组...") {@Overridepublic void uncaughtException(Thread t, Throwable e) {super.uncaughtException(t, e);log.error("线程组内捕获到线程[{},{}]异常", t.getId(), t.getName(), e);}};ExceptionInThreadGroup exceptionInThreadGroup = new ExceptionInThreadGroup();new Thread(threadGroup, exceptionInThreadGroup, "线程1").start();TimeUnit.MILLISECONDS.sleep(300L);//优先获取绑定在thread对象上的异常处理器Thread thread = new Thread(threadGroup, exceptionInThreadGroup, "线程2");thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());thread.start();TimeUnit.MILLISECONDS.sleep(300L);new Thread(threadGroup, exceptionInThreadGroup, "线程3").start();}
}
  • 结果:

4. 线程池

/*** @author futao* @date 2020/6/17*/
public class CatchThreadPoolException {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,4,1L,TimeUnit.MINUTES,new LinkedBlockingDeque<>(1024),new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);//设置线程异常处理器thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());return thread;}});threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {throw new RuntimeException("execute()发生异常");}});threadPoolExecutor.submit(new Runnable() {@Overridepublic void run() {throw new RuntimeException("submit.run()发生异常");}});threadPoolExecutor.submit(new Callable<String>() {@Overridepublic String call() throws Exception {throw new RuntimeException("submit.call()发生异常");}});threadPoolExecutor.shutdown();}
}
  • 结果: 并不符合预期,预期应该捕获三个异常

    • 只捕获到了通过execute()提交的任务的异常

    • 没有捕获到通过submit()提交的任务的异常

  • 通过afterExecute()捕获submit()任务的异常

    • 通过submit()方法的源码可以发现,submit()是将runnable()封装成了RunnableFuture<Void>,并最终调用execute(ftask);执行。

/*** @author futao* @date 2020/6/17*/
@Slf4j
public class CatchThreadPoolException {public static void main(String[] args) throws InterruptedException, ExecutionException {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,4,1L,TimeUnit.MINUTES,new LinkedBlockingDeque<>(1024),new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);//设置线程异常处理器thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());return thread;}}) {/*** 捕获{@code FutureTask<?>}抛出的异常** @param r* @param t*/@Overrideprotected void afterExecute(Runnable r, Throwable t) {super.afterExecute(r, t);if (r instanceof FutureTask<?>) {try {//get()的时候会将异常内的异常抛出((FutureTask<?>) r).get();} catch (InterruptedException e) {e.printStackTrace();Thread.currentThread().interrupt();} catch (ExecutionException e) {log.error("捕获到线程的异常返回值", e);}}//Throwable t永远为null,拿不到异常信息//log.error("afterExecute中捕获到异常,", t);}};threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {throw new RuntimeException("execute()发生异常");}});TimeUnit.MILLISECONDS.sleep(200L);threadPoolExecutor.submit(new Runnable() {@Overridepublic void run() {throw new RuntimeException("submit.run()发生异常");}});TimeUnit.MILLISECONDS.sleep(200L);threadPoolExecutor.submit(new Callable<String>() {@Overridepublic String call() throws Exception {throw new RuntimeException("submit.call()发生异常");}}).get();   //get()的时候会将异常抛出threadPoolExecutor.shutdown();}
}

欢迎在评论区留下你看文章时的思考,及时说出,有助于加深记忆和理解,还能和像你一样也喜欢这个话题的读者相遇~

# 本文源代码

  • https://github.com/FutaoSmile/learn-thread/tree/master/src/main/java/com/futao/learn/threads/捕获线程异常

Java多线程:捕获线程异常相关推荐

  1. Java并发-UncaughtExceptionHandler捕获线程异常信息并重新启动线程

    Java并发-UncaughtExceptionHandler捕获线程异常信息并重新启动线程 参考文章: (1)Java并发-UncaughtExceptionHandler捕获线程异常信息并重新启动 ...

  2. Java多线程之线程池的手写改造和拒绝策略

    Java多线程之线程池的手写改造和拒绝策略 目录 自定义线程池的使用 四种拒绝策略代码体现 1. 自定义线程池的使用 自定义线程池(拒绝策略默认AbortPolicy) public class My ...

  3. Java多线程之线程池7大参数、底层工作原理、拒绝策略详解

    Java多线程之线程池7大参数详解 目录 企业面试题 线程池7大参数源码 线程池7大参数详解 底层工作原理详解 线程池的4种拒绝策略理论简介 面试的坑:线程池实际中使用哪一个? 1. 企业面试题 蚂蚁 ...

  4. UncaughtExceptionHandler:java未捕获的异常/错误默认处理方式

    UncaughtExceptionHandler:java未捕获的异常/错误 @(JAVA)[java] java有一部分异常属于运行时异常,这类异常发生时通过会导致程序发生错误,导致退出.如数据下标 ...

  5. Java多线程:线程安全和非线程安全的集合对象

    转载自  Java多线程:线程安全和非线程安全的集合对象 一.概念: 线程安全:就是当多线程访问时,采用了加锁的机制:即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到 ...

  6. java多线程与线程间通信

    转自(http://blog.csdn.net/jerrying0203/article/details/45563947) 本文学习并总结java多线程与线程间通信的原理和方法,内容涉及java线程 ...

  7. Java多线程与线程并发库高级应用笔记

    以下内容是学习张老师Java多线程与线程并发库高级应用时所做的笔记,很有用 网络编辑器直接复制Word文档排版有点乱,提供原始文件下载 先看源文件概貌 张孝祥_Java多线程与并发库高级应用 [视频介 ...

  8. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  9. Java多线程之线程池配置合理线程数

    Java多线程之线程池配置合理线程数 目录 代码查看公司服务器或阿里云是几核的 合理线程数配置之CPU密集型 合理线程数配置之IO密集型 1. 代码查看公司服务器或阿里云是几核的 要合理配置线程数首先 ...

最新文章

  1. 谈谈Python那些不为人知的冷知识(一)
  2. Django博客系统(文章模型)
  3. OSI参考模型和网络编程
  4. 37个JavaScript基本面试问题和解答
  5. fopen php 乱码,如何解决php fgets读取文件乱码的问题
  6. React开发(125):ant design学习指南之form中的hasFeedback
  7. 三款新功能发布,助力阿里云表格存储再次升级
  8. SQL Server 2000/2005 数据库分页
  9. python 监视文件目录
  10. cl_salv_table
  11. k8s Deployment
  12. Hadoop集群部署模型纵览3
  13. HDU 1260: Tickets
  14. 如何将m3u8格式转成MP4以及可播放格式
  15. Zookeeper序列化及通信协议
  16. AS问题解决 Not recognizing known sRGB profile
  17. Self Service Password部署
  18. ArcGIS中的土地利用变化分析
  19. 黑马程序员--IDEA版本2018Java基础班+就业班大牛编程吧
  20. 分布式任务调度中心xxl-job

热门文章

  1. MPL,MIL和MCL
  2. 关于 TCP 并发连接的几个思考题与试验
  3. 支持向量机(三)核函数
  4. Norse Attack Map网络攻击实时监测地图
  5. 背完这444句英语,你的口语绝对不成问题了
  6. python 星号*使用方法
  7. 来自一名程序员的表白
  8. SSM 框架 Maven项目整合实例
  9. WIN10系统触摸板快捷键
  10. 二维绘图引擎:圆、半圆、弧线绘制