当前位置: 首页 > news >正文

【JUC】线程池

一、线程池是什么?

线程池本质上是一个管理线程的组件

它提前创建或按需创建一批线程,把线程维护起来。后续有任务提交时,不是每次都创建新线程,而是把任务交给线程池中的线程去执行。

这样做的核心目的有三个:

  1. 线程复用

    避免频繁创建和销毁线程。

  2. 资源控制

    限制线程数量,防止无限制创建线程把 CPU、内存打爆。

  3. 任务管理

    通过阻塞队列、拒绝策略等机制,对任务进行排队、限流和兜底处理。

所以线程池不是单纯为了“快”,更重要的是为了可控地并发执行任务

二、线程池的七个核心参数

Java 线程池通常指的是ThreadPoolExecutor,它有七个重要参数:

public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler )

可以这样理解:

1. corePoolSize:核心线程数

线程池中长期保留的线程数量。

只要当前线程数小于核心线程数,有新任务提交时,就会优先创建核心线程执行任务。

2. maximumPoolSize:最大线程数

线程池允许创建的最大线程数量。

最大线程数 = 核心线程 + 临时线程。

3. keepAliveTime:临时线程空闲存活时间

当线程池中的线程数量超过核心线程数时,多出来的临时线程如果空闲时间超过keepAliveTime,就会被回收。

4. unit:时间单位

比如秒、毫秒、分钟等。

5. workQueue:任务阻塞队列

当核心线程都在执行任务时,新任务会进入阻塞队列等待。

常见队列有:

ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue PriorityBlockingQueue

6. threadFactory:线程工厂

用于自定义线程创建方式。

实际开发中经常用它给线程命名,例如:

order-pool-1 order-pool-2

这样线上排查问题时更容易定位。

7. handler:拒绝策略

当线程池和队列都满了,无法再接收任务时,就会触发拒绝策略。

三、线程池执行任务的流程

可以记成一句话:

先核心线程,再任务队列,再临时线程,最后拒绝策略。

具体流程是:

  1. 提交任务后,如果当前线程数小于corePoolSize,创建核心线程执行任务。
  2. 如果核心线程都在忙,则把任务放入阻塞队列。
  3. 如果阻塞队列也满了,并且当前线程数小于maximumPoolSize,则创建临时线程执行任务。
  4. 如果当前线程数已经达到maximumPoolSize,并且队列也满了,就触发拒绝策略。

这里有一个重要细节:

不是一提交任务就创建到最大线程数。

线程池是先用核心线程,再入队,队列满了之后才创建临时线程。

所以队列大小会直接影响线程池扩容行为。

四、四种常见拒绝策略

1. AbortPolicy

默认策略。

任务无法提交时,直接抛出RejectedExecutionException

适合希望快速感知异常的场景,比如核心业务任务不能静默丢失。

但是需要业务方捕获异常,否则可能影响主流程。


2. DiscardPolicy

直接丢弃新任务,不抛异常。

这种策略风险比较大,因为任务丢了也没有任何提示。

适合一些允许丢失的非核心任务,比如:

心跳上报 埋点统计 临时状态刷新

3. DiscardOldestPolicy

丢弃队列中最早的任务,然后尝试提交当前新任务。

适合更关注新数据的场景,比如:

实时行情刷新 实时监控数据 实时位置上报

但是如果任务之间有顺序依赖,不能用这个策略。


4. CallerRunsPolicy

由提交任务的线程自己执行这个任务。

比如主线程提交任务到线程池,线程池满了,这个任务就由主线程自己执行。

它的好处是:

不会丢任务 不会抛异常 可以反向降低任务提交速度

因为调用线程自己去执行任务后,提交新任务的速度自然会变慢,相当于一种简单的削峰限流。

适合不希望任务丢失,但可以接受响应变慢的场景。

五、核心线程数和最大线程数怎么设置?

这个问题不能死背公式,要结合业务类型和压测结果。

1. CPU 密集型任务

比如:

复杂计算 加密解密 图片处理 规则计算

这类任务主要消耗 CPU,线程太多反而会带来上下文切换开销。

常见参考值:

线程数 = CPU 核数 + 1

加 1 是为了在个别线程阻塞时,仍然有线程可以继续利用 CPU。


2. I/O 密集型任务

比如:

数据库查询 Redis 访问 HTTP 调用 文件读写 MQ 消费

这类任务大量时间在等待 I/O,CPU 并不是一直忙,所以可以设置更多线程。

常见参考值:

线程数 = 2 * CPU 核数

更通用的公式是:

线程数 = CPU 核数 * (1 + 线程等待时间 / 线程运行时间)

注意你原文里写的是:

线程数 = CPU核数 * (1 + 线程等待时间 / 线程运行总时间)

这里建议改成:

线程数 = CPU 核数 * (1 + 等待时间 / 计算时间)

因为这个公式关注的是:线程等待时间相对于真正占用 CPU 计算时间的比例。

等待时间越长,说明线程越经常阻塞,就可以配置更多线程。

六、为什么不能硬套公式?

因为实际应用很复杂:

一个应用里可能有很多线程池,比如:

Tomcat 工作线程 Dubbo/RPC 线程 数据库连接池线程 MQ 消费线程 业务异步线程池 定时任务线程池 GC 线程

如果每个线程池都按公式配置很多线程,总线程数可能非常大,反而导致:

上下文切换频繁 CPU 利用率异常 内存占用升高 响应时间变长 甚至 OOM

所以合理做法是:

  1. 根据任务类型设置初始值;
  2. 通过压测观察吞吐量、响应时间、CPU、内存、队列堆积;
  3. 结合线上监控继续调整;
  4. 最终找到适合当前业务的参数。

七、上线后关注哪些线程池指标?

你这部分写得挺好,可以稍微压缩成面试表达。

上线后主要关注四类指标:

1. 线程维度

包括:

核心线程数 当前线程数 活跃线程数 最大线程数 线程创建和销毁频率

如果活跃线程数长期接近最大线程数,说明线程池压力比较大。


2. 队列维度

包括:

队列长度 队列剩余容量 任务等待时间

如果队列长期堆积,说明任务提交速度大于处理速度。

可能原因有:

线程数太少 任务执行太慢 下游服务变慢 数据库慢 SQL 锁竞争严重

3. 任务维度

包括:

任务提交速率 任务完成速率 任务平均执行耗时 任务最大执行耗时 任务失败数

如果提交速率长期大于完成速率,说明线程池迟早会堆满。


4. 异常维度

包括:

拒绝任务数 任务异常数 线程阻塞时间 死锁检测

拒绝任务数是非常重要的告警指标。

一旦出现拒绝,说明线程池已经到达瓶颈,需要立刻排查是流量突增、任务变慢,还是配置不合理。

八、为什么不推荐使用 Executors 创建线程池?

这个问题很常见。

Executors虽然用起来方便,但它隐藏了很多关键参数,容易导致资源不可控。

1. newFixedThreadPool 和 newSingleThreadExecutor

它们使用的是无界队列:

LinkedBlockingQueue

默认容量接近:

Integer.MAX_VALUE

如果任务提交速度大于任务处理速度,队列会不断堆积,最终可能导致 OOM。


2. newCachedThreadPool

它使用的是:

SynchronousQueue

并且最大线程数是:

Integer.MAX_VALUE

如果瞬间来了大量任务,线程池可能疯狂创建线程,最终导致:

线程数过多 CPU 上下文切换严重 内存耗尽 OOM

3. 推荐方式

推荐直接使用ThreadPoolExecutor,显式指定:

核心线程数 最大线程数 队列大小 线程工厂 拒绝策略

这样线程池资源是可控的。

例如:

ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("order-pool-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy() );

当然,如果不用 Guava,也可以自己实现ThreadFactory

九、面试回答

线程池主要是为了复用线程、控制资源和管理任务。它通过核心线程数、最大线程数、阻塞队列、线程工厂和拒绝策略等参数来控制任务执行流程。任务提交后,会先创建核心线程执行;核心线程满了之后进入阻塞队列;队列满了之后再创建临时线程;如果线程数达到最大值,就执行拒绝策略。

核心线程数和最大线程数的设置需要结合任务类型。CPU 密集型任务一般设置为 CPU 核数加一,I/O 密集型任务可以设置得更大,比如两倍 CPU 核数,但这些只是参考值,最终还是要结合压测和线上监控来调优。

实际开发中不推荐使用 Executors 创建线程池,因为它可能使用无界队列或者允许创建过多线程,存在 OOM 风险。更推荐直接使用 ThreadPoolExecutor,明确指定队列大小、线程数量和拒绝策略。

上线后需要重点关注活跃线程数、队列长度、任务耗时、任务完成速率、拒绝任务数等指标。如果业务流量变化较大,还可以引入动态线程池,把核心参数放到配置中心,实现在线调整和告警监控。

http://www.jsqmd.com/news/862741/

相关文章:

  • [Unity实战] Shader 学了很多却提不动项目性能?问题往往出在没把渲染知识接回场景优化
  • 2026最新诚信优选 重庆市万州区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 2026年京东云OpenClaw/Hermes Agent配置Token Plan搭建保姆教程
  • 2026最新诚信优选 重庆市武隆区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 2026最新诚信优选 临汾市尧都区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • okbiye 双降神器:把论文重复率 + AIGC 率 “一键清零”,毕业季再也不用慌
  • C++内存对齐与布局优化
  • 2026最新诚信优选 重庆市南川区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 2026最新诚信优选 上海市崇明区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 2026最新诚信优选 重庆市永川区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 【AI入门知识点】Skills 是什么?终于有人把 Skills、Function Calling、MCP 讲明白了
  • 一键营造立体感!OBS“半透明滤镜”上线,让直播间层次分明
  • 2026最新诚信优选 重庆市綦江区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 2026最新诚信优选 吕梁市离石区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 论文降重 + 消 AIGC 双难题?用 okbiye,我帮室友一周搞定毕业查重危机
  • TCP拥塞控制详解
  • 2026最新诚信优选 重庆市渝北区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • [网鼎杯 2020 朱雀组]Nmap-保姆级讲解
  • 《数据挖掘(主编:吕欣 王梦宁)》读书笔记:异常检测方法梳理与实践理解
  • 2026最新诚信优选 南京市高淳区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 论文双检卡脖子?okbiye 降重 + 去 AIGC 双 buff,助你一键通关毕业审核
  • 计算机二级 WPS 文字题:样式调整考点 详细解析
  • Agent的“记忆”与“约束”工程---->Agent协作
  • 2026最新诚信优选 重庆市渝中区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • RAG三大冲突与三大死穴及解决方案
  • HTTPS一文通
  • 2026最新诚信优选 南京市鼓楼区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收
  • 毕业季 “双率” 自救:okbiye 论文降重 | 降 AIGC,让你告别查重焦虑
  • 30天AI小白变高手:从零开始掌握AI画图、写文、做视频、提效变现!
  • 2026最新诚信优选 重庆市长寿区黄金回收白银回收铂金回收彩金回收门店TOP5排行榜+联系方式推荐_转自TXT - 盛世金银回收