Java线程池中线程异常后:是销毁还是复用?

ce670bfec28ce037c5bac77b02f8cfa5.jpeg来源:blog.51cto.com/u_15714439/10238518

👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 /  赠书福利

全栈前后端分离博客项目 2.0 版本完结啦, 演示链接http://116.62.199.48/ ,新项目正在酝酿中。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。目前已更新了239小节,累计38w+字,讲解图:1645张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,已有1200+小伙伴加入(早鸟价超低)

038799c48c3fa9e93374b54172e0820b.gif

一个线程池中的线程异常了,那么线程池会怎么处理这个线程?

需要说明,本文的线程池都是java.util.concurrent.ExecutorService线程池,本文将围绕验证,阅读源码俩方面来解析这个问题。

代码验证

验证execute提交线程池中

测试代码:

public class ThreadPoolExecutorDeadTest {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = buildThreadPoolExecutor();
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute-exception"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));


        Thread.sleep(5000);
        System.out.println("再次执行任务=======================");

        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
    }


    public static ExecutorService buildThreadPoolExecutor() {
        return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build()
                , new ThreadPoolExecutor.CallerRunsPolicy());
    }

    private static void exeTask(String name) {
        String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
        if ("execute-exception".equals(name)) {
            throw new RuntimeException(printStr + ", 我抛异常了");
        } else {
            System.out.println(printStr);
        }
    }
}

执行结果如下:

b3e6ffd7d1b1c995e40d18f53b684aaf.jpeg

图片

结论:

execute 提交到线程池的方式,如果执行中抛出异常,并且没有在执行逻辑中catch,那么会抛出异常,并且移除抛出异常的线程,创建新的线程放入到线程池中。

验证submit提交线程池中

测试代码:

public class ThreadPoolExecutorDeadTest {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = buildThreadPoolExecutor();
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute-exception"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));


        Thread.sleep(5000);
        System.out.println("再次执行任务=======================");

        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
    }


    public static ExecutorService buildThreadPoolExecutor() {
        return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build()
                , new ThreadPoolExecutor.CallerRunsPolicy());
    }

    private static void exeTask(String name) {
        String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
        if ("execute-exception".equals(name)) {
            throw new RuntimeException(printStr + ", 我抛异常了");
        } else {
            System.out.println(printStr);
        }
    }
}

执行结果如下:

72aa3a95d93d501487b9d782e9300959.jpeg

图片

结论:

submit 提交到线程池的方式,如果执行中抛出异常,并且没有catch,不会抛出异常,不会创建新的线程。

源码解析

1 java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable)

0a0702b70e4a9777d30c276e7ff17e23.jpeg

图片

2 查看execute方法的执行逻辑java.util.concurrent.ThreadPoolExecutor#runWorker

eb1e99c8d5ccf02a948a09f906484be3.jpeg

图片

3 java.util.concurrent.ThreadPoolExecutor#processWorkerExit

efa8be6bd199622aca0a052dc2edba71.jpeg

图片

可以发现,如果抛出异常,会移除抛出异常的线程,创建新的线程。

4 为什么submit方法,没有创建新的线程,而是继续复用原线程?

还记得,我们在3.1的时候,发现submit也是调用了execute方法,但是在调用之前,包装了一层 RunnableFuture,那一定是在RunnableFuture的实现 FutureTask中有特殊处理了,我们查看源码可以发现。

4fefe4942cc16f768ac9b86f66495034.jpeg

图片

ae31e4bc84918c743f04b6d2ff074e28.jpeg

图片

bd4799982c04dc15132f167b372fdaca.jpeg

图片

3c59bb1fdaa8666ec88b4092519ef3fd.jpeg

图片

但是,我们通过java.util.concurrent.FutureTask#get(),就可以获取对应的异常信息。

总结

当一个线程池里面的线程异常后:

  • 当执行方式是execute时,可以看到堆栈异常的输出,线程池会把这个线程移除掉,并创建一个新的线程放到线程池中。

  • 当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常,不会把这个线程移除掉,也不会创建新的线程放入到线程池中。

以上俩种执行方式,都不会影响线程池里面其他线程的正常执行。

👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 /  赠书福利

全栈前后端分离博客项目 2.0 版本完结啦, 演示链接http://116.62.199.48/ ,新项目正在酝酿中。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。目前已更新了239小节,累计38w+字,讲解图:1645张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,已有1200+小伙伴加入(早鸟价超低)

673e6021ed140759df88c7cbf47d6a1a.gif

22166ecba19f6316af2afd6616d1051c.jpeg

 
 

700a7f6460ffd98184d9dc64d11caffa.gif

 
 
 
 
1. 我的私密学习小圈子~
2. 万字详解,带你彻底掌握 WebSocket 用法(至尊典藏版)
3. 海量请求下的接口并发解决方案
4. 这 30 款 IDEA 宝贝插件 yyds
 
 
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值