面试题|线程池里有几个线程在运行

news/2025/1/30 13:20:07/文章来源:https://www.cnblogs.com/east7/p/18692165

  本文主要改编自https://www.sohu.com/a/391767502_355142。下面从一道面试题引入本文主题~~

面试官:"假设有一个线程池,核心线程数为10,最大线程数为20,任务队列长度为100。如果现在来了100个任务,那么线程池里有几个线程在运行?"
粉丝豪:"应该是10吧!"
面试官:"你确定?"
粉丝豪:"确定啊!就是10…"

  于是乎,漂亮的HR小姐姐让粉丝豪回去等通知了~

  大家如果看出来了此题的陷阱,就不用看本文了!其实,这道题正确的答案是"不一定!"因为并没指明是哪一种线程池机制,带着这个疑问继续往下看!我们基于jdk 8,以两类线程池机制——先放队列再创建线程和先创建线程再放入队列——来剖析这道面试题。

先放队列再创建线程

  核心线程数用完后,新来的任务先进队列,在队列满的时候,再创建线程。这种情况是大家最容易想到的情况,因为JDK中的线程池,也就是ThreadPoolExecutor就是这种机制!OK,我们先来看一下ThreadPoolExecutor的void execute(Runnable command)方法源码,如下图所示:

cacc6b53a95df805e9b3ff1474ae0360.jpeg

  在int c = ctl.get()代码上方,折叠了如下所示的一段英文注释,解释了上述截图中的三步流程:

    /** Proceed in 3 steps:** 1. If fewer than corePoolSize threads are running, try to* start a new thread with the given command as its first* task.  The call to addWorker atomically checks runState and* workerCount, and so prevents false alarms that would add* threads when it shouldn't, by returning false.** 2. If a task can be successfully queued, then we still need* to double-check whether we should have added a thread* (because existing ones died since last checking) or that* the pool shut down since entry into this method. So we* recheck state and if necessary roll back the enqueuing if* stopped, or start a new thread if there are none.** 3. If we cannot queue task, then we try to add a new* thread.  If it fails, we know we are shut down or saturated* and so reject the task.*/

  如果对英文不感冒,请参考下面的中文翻译:

  1. 判断当前活跃线程数是否小于corePoolSize,如果小于,则调用addWorker创建线程执行任务;
  2. 如果不小于corePoolSize,则将任务添加到workQueue队列;
  3. 如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用函数reject拒绝接受任务。

  用一张流程图来解释,如下:

f61a17cde87a4d871323d0d04a05c096.jpeg

  如图所示,默认的机制为线程池里的核心线程数不够了,后面进来的任务会先丢队列,当队列满了,才起新线程。

  因此,按照这套机制!粉丝豪的回答是正确的,当有100个任务添加进来时,先创建10个核心线程,剩下90个任务都丢进阻塞队列,因此线程池里只有10个线程在执行任务!

先创建线程再放入队列

  当核心线程数用完后,如果来了新任务,则先创建线程,直至达到最大线程数,再把新任务放入阻塞队列。在dubbo中,有一种这种机制的线程池叫EagerThreadPoolExecutor线程池;在Tomcat里面也有类似的线程池。

  来看一下EagerThreadPoolExecutor源码:

public class EagerThreadPoolExecutor extends ThreadPoolExecutor {/*** task count*/private final AtomicInteger submittedTaskCount = new AtomicInteger(0);public EagerThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, TaskQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}/*** @return current tasks which are executed*/public int getSubmittedTaskCount() {return submittedTaskCount.get();}@Overrideprotected void afterExecute(Runnable r, Throwable t) {submittedTaskCount.decrementAndGet();}@Overridepublic void execute(Runnable command) {if (command == null) {throw new NullPointerException();}// do not increment in method beforeExecute!submittedTaskCount.incrementAndGet();try {super.execute(command);} catch (RejectedExecutionException rx) {// retry to offer the task into queue.final TaskQueue queue = (TaskQueue) super.getQueue();try {//将任务提交到队列中if (!queue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {submittedTaskCount.decrementAndGet();throw new RejectedExecutionException("Queue capacity is full.", rx);}} catch (InterruptedException x) {submittedTaskCount.decrementAndGet();throw new RejectedExecutionException(x);}} catch (Throwable t) {// decrease any waysubmittedTaskCount.decrementAndGet();throw t;}}
}

  主要重写了ThreadPoolExecutor的函数void execute(Runnable command),如果触发拒绝策略,那么将任务提交到TaskQueue阻塞队列中,再看TaskQueue源码:

public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {private static final long serialVersionUID = -2635853580887179627L;private EagerThreadPoolExecutor executor;public TaskQueue(int capacity) {super(capacity);}public void setExecutor(EagerThreadPoolExecutor exec) {executor = exec;}@Overridepublic boolean offer(Runnable runnable) {if (executor == null) {throw new RejectedExecutionException("The task queue does not have executor!");}int currentPoolThreadSize = executor.getPoolSize();// have free worker. put task into queue to let the worker deal with task.//如果提交任务数小于当前工作线程数,说明当前工作线程足够处理任务,将提交的任务插入到工作队列if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {return super.offer(runnable);}// return false to let executor create new worker.//重写代码的精髓之处:如果提交任务数大于当前工作线程数并且小于最大线程数,说明提交的任务量线程已经处理不过来,那么需要增加线程数,返回falseif (currentPoolThreadSize < executor.getMaximumPoolSize()) {return false;}// currentPoolThreadSize >= max//工作线程数到达最大线程数,插入到workqueuereturn super.offer(runnable);}/*** retry offer task** @param o task* @return offer success or not* @throws RejectedExecutionException if executor is terminated.*/public boolean retryOffer(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {if (executor.isShutdown()) {throw new RejectedExecutionException("Executor is shutdown!");}return super.offer(o, timeout, unit);}
}

  主要重写了LinkedBlockingQueue的offer方法,而if (!queue.retryOffer(command, 0, TimeUnit.MILLISECONDS))则调用offer,保证在当前线程数小于最大线程数时,直接新增线程

  因此,如果按照这么一套机制,粉丝豪的答案就不正确了。线程池启动完毕后进来100个任务时,直接会起20个线程,剩下的80个任务都会被丢进阻塞队列,综上所述,现在线程池里有20个线程在运行。

Reference

  • https://blog.csdn.net/qq_26418435/article/details/102661092
  • dubbo四种线程池源码分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/876098.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开关电源1

EMI(参考链接)从左到右分别是安全电容(X电容),共模电感和安全电容(Y电容),黑色线是火线(L),白色线是零线(N),绿色线是地线(G) 大黄块:安全电容X电容,接在火线和零线之间,用于抑制差模干扰 红白相间线圈:共模电感(4个引脚),绕制方法是双线双向,作用是抑…

deepin 25 Preview 安装及体验

deepin 25 Preview(预览版)近期发布。本文让我们一起体验安装和使用感受吧! 下载下载建议用种子文件下载。作为国内屈指可数的厂商,也不套下CDN,下载也仅2M图片接下来,创建虚拟机。(根据自身情况配置虚拟机性能)选Debian系列 注意,安装磁盘容量至少70G 开始安装root登…

02人工智能创新型教师培育计划(第一期)0126

人工智能创新型教师培育计划(第一期)活动更新(1月24日 15:00更新): 感谢各位老师对本次活动的关注与支持,线上课程即将开始,请各位已报名老师注意以下事项: 1. 直播时间:1月25日 19:30—21:00 1月26日 19:30—21:00 2. 直播内容:课题:大模型赋能3小时入门Pyth…

java基础Day7 面向对象(2)

六、继承 Inheritance 6.1 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。 extends:扩展。子类(派生类)是父类(基类)的扩展。 继承是类与类之间的关系。 java中只有单继承,没有多继承:一个儿子只能有一个爸爸,一个爸爸可以有多个儿子。Inheritance>…

java基础Day7 面向对象

六、继承 Inheritance 6.1 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。 extends:扩展。子类(派生类)是父类(基类)的扩展。 继承是类与类之间的关系。 java中只有单继承,没有多继承:一个儿子只能有一个爸爸,一个爸爸可以有多个儿子。Inheritance>…

[每日 C] Remove Exactly Two

前言 做一下一场没打的 \(\textrm{div 2}\) 的 \(\rm{C}\) 最近思维能力还在下降, 无敌 前天还能打出思维题, 今天打不出 \(\textrm{div 2 C}\) 思路 首先转化题意给定一个 \(n\) 节点的树, 求删除两个节点及其连边之后, 最大连通块的数量不难发现删除一个节点, 会把树分成几个…

物体检测

滑窗 卷积实现的滑窗参考:Convolutional Implementation of Sliding Windows | Coursera\[y = \begin{bmatrix} p_c \\ b_x \\ b_y \\ b_h \\ b_w \\ c_1 \\ c_2 \\ c_3 \end{bmatrix} \]

k8s启用ingress错误案例

ingress-nginx访问流程:问题: 部署2个nginx-deployment内容v1&&v2,结合ingress作为入口分别访问。访问ingress-入口失败 排错步骤: 1、首先确认2个nginx访问无问题 2、确认宿主机访问不问题 3、访问ingress一直无法连接服务器 到这一步 找不到服务器地址 cm到后端…

Python-100-Days|Python - 100天从新手到大师|学习笔记

Python-100-Days Make English as your working language. (让英语成为你的工作语言) Practice makes perfect. (熟能生巧) All experience comes from mistakes. (所有的经验都源于你犯过的错误) Dont be one of the leeches. (不要当伸手党) Either outstanding or o…

集训2 20240124

集训2 20240124 牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ A: 题目大意:给出 \(7\) 个数,判断有没有 \(4,7\) #include<bits/stdc++.h>using namespace std;int main() {int a[7];for (int i=0;i<7;i++) cin>>a[i];for (int i=0;i<…

【新手必看】PyCharm2025 免费下载安装配置教程+Python环境搭建、图文并茂全副武装学起来才嗖嗖的快,绝对最详细!

🚀 个人主页 极客小俊 ✍🏻 作者简介:web开发者、设计师、技术分享 🐋 希望大家多多支持, 我们一起学习和进步! 🏅 欢迎评论 ❤️点赞💬评论 📂收藏 📂加关注PyCharm 介绍 🌰 Pycharm是由JetBrains打造的一款专门用于编写和开发Python应用程序的集成开发环境…

nginx配置证书和私钥进行SSL通信验证

nginx配置证书和私钥进行SSL通信验证@目录一、背景1.1 秘钥和证书是两个东西吗?1.2 介绍下nginx配置文件中参数ssl_certificate和ssl_certificate_key1.3介绍下nginx支持的证书类型1.4 目前nginx支持哪种证书格式?1.5 nginx修改配置文件目前方式也会有所不同1.6 介绍下不通格…