面试官问:Java线程池执行的用户任务抛出异常会怎样?

ThreadPoolExecutor.execute

源码分析

看源码可以知道,ThreadPoolExecutor中的任务都是在runWorker中执行的

fd1892740c8db9ca57c07cf0a4061073.png

图片

通过源码可以看到

  • 1149行执行用户任务

  • 1150~1155处理捕获任务异常,并抛出

  • 抛出异常后会退出,从任务队列中拉取任务的循环

  • 然后执行1167行,worker线程退出的逻辑

看一下线程退出的逻辑

657b50fd7701e23627c3af127e570b2b.png

图片
  • 如果是异常退出,参数completedAbruptly为true

  • 如果状态值比STOP小,即线程池没有停止,会重新创建一个worker线程

总结

ThreadPoolExecutor.execute 如果用户任务抛出了异常,在线程池运行的状态下,会重新创建一个worker线程。

这里就可能存在一个风险,即如果用户任务大量的抛出异常,可能会导出线程资源频繁的销毁、创建。

因此,需要用户任务应当主动对异常进行处理,而不是消极的抛给线程池。

ThreadPoolExecutor.submit

源码分析

通过 ThreadPoolExecutor.submit 提交的用户任务,会包装成一个FutureTask,返回一个Future对象。因此异常处理的逻辑和ThreadPoolExecutor.execute有些差别。Java新特性系列教程:https://www.yoodb.com/java/characteristic/java-8/java8-stream.html

看一个FutureTask.run方法

910c811a6fe3a1ee3e3becb3ca8a92bc.png

图片

从源码可以看到,FutureTask执行用户任务,如果异常,不会对外抛出,仅是记录

但是在调用Future.get时,会抛出异常,但此时的线程不是线程池的线程了,而是用户线程,因此对线程池是友好的。

总结

ThreadPoolExecutor.submit 提交的用户任务,会包装成一个FutureTask,FutureTask执行用户任务,如果异常,不会对外抛出,仅是记录,但是在调用Future.get时,会抛出异常。

异常是在用户线程中抛出的,因此不影响线程池中的线程。

ScheduledThreadPoolExecutor.schedule

源码分析

ScheduledThreadPoolExecutor.schedule会将用户任务包装为ScheduledFutureTaskScheduledFutureTask是FutureTask的子类,看下ScheduledFutureTask的执行逻辑

2f30f6452a0500328e636aa441e04b47.png

图片

ScheduledFutureTask是FutureTask的子类,所以有异常时,也不是抛出,而是记录

  • 293行对于非周期性任务,执行一次,如果有异常,不抛出,仅记录。面试宝典:https://www.yoodb.com/

  • 294~296对于周期性任务,执行完本次后,会设置下次执行的时间。如果本次出现异常,ScheduledFutureTask.super.runAndReset()返回结果为false(这个可以从源码看到),此时不会设置下次任务调度的时间了,因此会导致周期性任务失效的现象,并且异常信息不会抛出,也不会打印

总结

ScheduledThreadPoolExecutor.schedule提交的用户任务,如果出现异常,是不会抛出的,也不会打印。

对于非周期性任务和周期性任务,执行异常,都没有办法感知(不抛出、看不到)。

对于周期性任务,任何一次调度时,任务出现异常,都会导致后续无法调度。

因此,在使用这个线程池时,我们应当保证用户任务不会抛出异常到执行线程,避免任务调度失效,和异常排查困难等问题。

思考:ThreadPoolExecutor.execute发生异常时为什么要退出

ThreadPoolExecutor.execute出现用户任务异常时,为什么要退出当前线程,再重新创建一个线程呢?

我思考了半天,觉得原因之一可能是:ThreadPoolExecutor的可以指定线程工厂,如果我的线程工厂是这样的

ThreadFactory threadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                // 处理异常的逻辑
            }
        });
        return thread;
    }
};

即在ThreadFactory创建线程时,指定了对未捕获的异常的处理器。

这种情况下,如果线程池不把发生异常的线程退出,可能会导致异常没有走到用户期望的逻辑上,因此需要将发生异常的线程退出,然后JVM调用UncaughtExceptionHandler

作者:lanicc

https://blog.csdn.net/qq_26824159/article/details/126767372

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!
最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!

Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!
------ 特别推荐 ------
特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。

点击“阅读原文”,了解更多精彩内容!文章有帮助的话,点在看,转发吧!
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值