通过《线程池内运行的线程抛异常,线程池会怎么办》了解到当线程执行的任务出现异常时,会将当前线程移出线程池,并新增一个线程到线程池中,我们先来回顾一下线程池的运行原理:

从原理图中可以看到只有当队列满了之后,才会去创建新的线程执行新加入的任务,那么到底有没有可能出现队列未满, 但是运行中的线程个数大于核心线程数?

理论上应该是不可能大于核心线程数,那么有没有意外呢?答案暂时不揭晓,我们先往下看,写几个demo测试一下,

public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> {Thread t = new Thread(r);t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.hashCode() + ", " + t1.getName() + " " + "发生了异常"));return t;});for (int i = 0; i < 30; i++) {int finalI = i;Thread t = new Thread(() -> {System.out.println(Thread.currentThread().hashCode() + ": " + pool.getQueue().size() + ", " + (10 / finalI));});pool.execute(t);}
}

运行结果

​ 第一列为线程hashcode,第二列为阻塞队列中的任务个数,第三列为计算数字

从控制台的输出能看到一共出现了4个不同的hashcode,也就表示创建过4个线程,然后线程1869318657执行任务时出现了异常,也就是除0异常,然后将其从线程池中移出,后续不再进行任务处理。从之前的文章中我们知道将异常的线程移除之后会重新创建一个线程加入到线程池中,那么正常情况下,线程池移除一个再加入一个,数量应该不变,但是从下面的输出看到这时同时有3个线程在处理任务,但是我们的核心线程数是2,为什么会出现3?

我们再来看下任务发生异常时的后续处理代码:

private void processWorkerExit(Worker w, boolean completedAbruptly) {// 从runWorker方法中传过来的是true,所以这句目前版本中必定会被执行到// 作用是将当前线程池中的有效线程数-1,意思也就是出现异常的线程会被从线程池中拿掉// 为什么说是出现异常的线程会被拿掉呢?因为在try内部是一个while循环,除非关闭核心线程或运行中线程出现异常,否则不会执行到这里if (completedAbruptly)decrementWorkerCount();final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 更新完成的任务数,只要是被线程池线程执行过的,不管是否出现异常,都被认为是执行成功的任务completedTaskCount += w.completedTasks;// 将当前Worker线程从线程池中移除销毁workers.remove(w);} finally {mainLock.unlock();}tryTerminate();// 一系列判断,主要是判断是否符合给线程池创建新的线程int c = ctl.get();if (runStateLessThan(c, STOP)) {if (!completedAbruptly) {// 如果核心线程允许超时回收,则不去创建线程,因为有新任务来的时候会自动创建int min = allowCoreThreadTimeOut ? 0 : corePoolSize;// 如果核心线程可以被回收,但是当前阻塞队列中不为空时,创建1个线程去执行任务if (min == 0 && ! workQueue.isEmpty())min = 1;// 校验当前运行的线程是否大于允许运行的线程数,比如核心线程数if (workerCountOf(c) >= min)return;}// ①...// 给线程池创建新的线程addWorker(null, false);}
}

我们看到在创建新线程时,会进行一系列判断,若判断全部通过,就会去给线程池的池子中创建一个新的工作线程,这里传入的core参数是false,也就是当成非核心线程来创建,难道问题出在这里?如果我此时运行的线程数小于等于核心线程数,那么再加进来一个非核心线程?我们来看看addWorker()方法的代码:

private boolean addWorker(Runnable firstTask, boolean core) {// goto语法retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);// 判断当前运行的线程个数是否超过了最大线程数if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctlif (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}// ...创建线程...return workerStarted;
}

从方法addWorker()的源码中我们看到,当我们参数core传一个false进来时,会去校验当前工作中的线程个数是否超过了最大线程数,如果没有超过的话则创建新的线程,问题就出在这里,为什么?

在方法processWorkerExit()中,我们对核心线程数进行的比较,但是如果在①处有新的任务调用execute()进来,则发现工作线程未达到核心线程数,这个时候就会去创建一个核心线程,创建之后,线程池中的工作线程就有2个了,然后我们从processWorkerExit()中传过来的请求会去校验工作线程个数是否超过了最大线程数,在我们的demo代码中是肯定不会超过的,那么就又会去创建一个线程放到线程池中,这样线程池中的工作线程就有3个了,具体流程图如下

正常流程 异常流程 工作线程数
execute()   0
—addWorker()   1
execute()   1
—addWorker()   2
  processWorkerExit() 2
  —remove() 1
execute()   1
—addWorker()   2
  —addWorker() 3

这就是为什么我们核心线程数是2,但是最终运行中的线程个数是3的原因


场景

  1. 任务尚未全部进入到队列中,队列未满

    public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> {Thread t = new Thread(r);t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.hashCode() + ", " + t1.getName() + " " + "发生了异常"));return t;});for (int i = 0; i < 30; i++) {int finalI = i;Thread t = new Thread(() -> {System.out.println(Thread.currentThread().hashCode() + ": " + pool.getQueue().size() + ", " + (10 / finalI));});pool.execute(t);}
    }
    

    这个也就是我们上面跑的demo,工作线程个数超出了核心线程数。

  2. 任务已经全部进入到队列中

    public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> {Thread t = new Thread(r);t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.hashCode() + ", " + t1.getName() + " " + "发生了异常"));return t;});for (int i = 0; i < 30; i++) {int finalI = i;Thread t = new Thread(() -> {if (finalI < 2) {try {// 前两个线程等待500毫秒,保证所有的任务都进入到队列中Thread.sleep(500);} catch (InterruptedException e) {}}System.out.println(Thread.currentThread().hashCode() + ": " + pool.getQueue().size() + ", " + (10 / (finalI - 4)));});pool.execute(t);}
    }
    

    我们看到当线程池开始执行任务时,已经把剩余的28个任务都放到队列中了,红色和蓝色下划线的线程是初始线程,当873出现异常后,将其移出线程池,然后创建一个新的线程执行队列中的剩余任务,因为这时所有的任务都在队列中等待被执行,execute()方法不会被调用,所以这种场景下,工作中的线程个数不会超过核心线程数。

  3. 任务尚未全部进入到队列中,队列已满,未达到最大线程数

    public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> {Thread t = new Thread(r);t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.hashCode() + ", " + t1.getName() + " " + "发生了异常"));return t;});for (int i = 0; i < 34; i++) {int finalI = i;Thread t = new Thread(() -> {if (finalI < 2) {try {// 等待任务全部进入到队列中,且创建完额外线程接收多出来的两个任务Thread.sleep(500);} catch (InterruptedException e) {}}System.out.println(Thread.currentThread().hashCode() + ": " + pool.getQueue().size() + ", "+ pool.getActiveCount() + ", " + (10 / (finalI - 33)));});pool.execute(t);}
    }
    

    运行结果

    ​ 第一列为线程hashcode,第二列为阻塞队列中的任务个数,第三列为当前的工作线程个数,第四列为计算数字

    从结果中我们看到创建了4个工作线程去执行任务,当线程230执行异常之后,创建了线程734继续执行,线程个数未超过最大线程数

  4. 任务尚未全部进入到队列中,队列已满,达到最大线程数

    这种情况就会依据拒绝策略执行相关的代码逻辑,线程数不会超过最大线程数


总结

从以上分析我们可以得出,只有在:任务尚未全部进入到队列中且队列未满的情况下,才会出现工作线程个数大于核心线程数,所以我们在使用线程池的过程中,待执行的任务尽量捕获所有的异常情况,不要将其抛出到线程池中的线程里。

线程池中运行的线程,当等待队列未满的情况下,一定不大于核心线程数吗相关推荐

  1. linux下c语言线程传参数,【linux】C语言多线程中运行线程池,在线程池中运行线程池,,传递的结构体参数值为空/NULL/0...

    C语言多线程中运行线程池,在线程池中运行线程池,,传递的结构体参数值为空/NULL/0 本贴问题,之前已经提问过一次,当时已经解决了,原贴在这里https://segmentfault.com/q/1 ...

  2. 线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程?线程池中线程复用原理

    1.一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务.阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使 ...

  3. c语言数据库线程池,C语言多线程中运行线程池,在线程池中运行线程池,,传递的结构体参数值为空/NULL/0...

    typedef struct { }LoanInfos; typedef struct{ int cp;//主线程编号 int thread;//线程编号 long int time; int arr ...

  4. 从源码角度解析线程池中顶层接口和抽象类

    摘要:我们就来看看线程池中那些非常重要的接口和抽象类,深度分析下线程池中是如何将抽象这一思想运用的淋漓尽致的. 本文分享自华为云社区<[高并发]深度解析线程池中那些重要的顶层接口和抽象类> ...

  5. 判断线程池中某个线程是否执行完成

    目录 1.先写结果 2.判断某个线程是否执行完成(不使用线程池) 3.在线程池中不能使用isAlive判断线程状态的原因 3-1.错误示例 3-2.创建线程工厂 3-3.创建线程方法(ThreadPo ...

  6. java 父子线程 调用链_ZipKin原理学习--Zipkin多线程及线程池中追踪一致性问题解决...

    在学习Zipkin分布式追踪系统中我们了解到Trace在整个调用链是一致的,在web服务中可以通过在header设置Trace值在不同的服务中进行传递,那样在一个服务内部不同的线程,甚至是线程池中Zi ...

  7. 线程池中各个参数如何合理设置

    欢迎大家关注我的公众号[老周聊架构],Java后端主流技术栈的原理.源码分析.架构以及各种互联网高并发.高性能.高可用的解决方案. 一.前言 在开发过程中,好多场景要用到线程池.每次都是自己根据业务场 ...

  8. 突然就懵了!面试官问我:线程池中多余的线程是如何回收的?

    点击关注公众号,Java干货及时送达 最近阅读了JDK线程池ThreadPoolExecutor的源码,对线程池执行任务的流程有了大体了解,实际上这个流程也十分通俗易懂,就不再赘述了,别人写的比我好多 ...

  9. [.Net线程处理系列]专题二:线程池中的工作者线程

    目录: 一.上节补充 二.CLR线程池基础 三.通过线程池的工作者线程实现异步 四.使用委托实现异步 五.任务 六.小结 一.上节补充 对于Thread类还有几个常用方法需要说明的. 1.1 Susp ...

最新文章

  1. Web Farm Web Garden
  2. C++输入输出进制、数据宽度与对齐、精度、取整
  3. [MATLAB学习笔记]Rng函数
  4. 安卓总结 之 OkHttp使用及源码分析(三)
  5. nifi 实现数据库到数据库之间数据同步
  6. 【2017年第2期】专题:大数据管理与分析
  7. 奈飞文化手册_奈飞文化手册学习笔记
  8. shell支持loop吗_Shell脚本case语句和loop语句,与,循环
  9. 餐饮小票在线生成_意大利电子小票发送截止日即将来临
  10. 图灵奖得主 Bengio:深度学习不会被取代!
  11. ubuntu下go插件delve下载安装
  12. ecshop添加404页面
  13. 学生适合做什么html网站,学生个人网页制作html
  14. python_(1)_向量运算
  15. “风味人间”与计算机程序设计艺术《禅与计算机程序设计艺术》 / 陈光剑
  16. 可恢复保险丝的内部结构
  17. base64编码解码讲解
  18. Hibernate的一些相关信息(续)
  19. 【机器学习】 随机森林(Random Forest)
  20. 怎么把c盘恢复出厂设置电脑语言,教你把电脑恢复出厂设置

热门文章

  1. ajax请求成功,失败处理!
  2. c51简单delay函数i的值跟延时的时间呈线性关系
  3. 关于Linux系统诞生发展历程、组成、特点、核心、发行版本
  4. 【proteus】proteus界面介绍
  5. 准备蓝桥杯--dyx--数列特征
  6. 【正点原子STM32连载】 第二十七章 RTC实时时钟实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
  7. 外汇交易的最佳时间点
  8. java运行错误java.lang.NoSuchMethodError: javax.persistence.OneToOne.orphanRemoval()Z
  9. linux mysql 双机热备_ORACLE 数据库双机热备方案(Linux)
  10. 学习红帽linux(RHCE)的就业前景