news 2026/7/9 8:26:29

你们项目里的线程池是怎么用的?怎么管理的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
你们项目里的线程池是怎么用的?怎么管理的?

平时写业务时好像没什么存在感,很多代码里随手就是一个:

java

ExecutorService executor = Executors.newFixedThreadPool(10);

看起来也能跑,任务也能异步执行,线上一开始也不一定会出问题。

但如果面试官问一句:你们项目里的线程池是怎么用的?怎么管理的?

这时候如果只回答一句“用Executors.newFixedThreadPool()”,基本就比较危险了。因为生产环境里,线程池不是简单创建几个线程来跑任务,而是要控制资源、控制队列、控制拒绝策略,还要能监控和调整。

本文内容:

  1. 为什么不建议直接使用Executors
  2. 常见内置线程池到底有什么问题
  3. ThreadPoolExecutor的几个核心参数怎么理解
  4. 生产环境里线程池一般怎么创建
  5. 项目中如何统一管理和监控线程池

为什么不建议直接使用 Executors

先用一张图把Executors的问题放到一起看:它的风险并不只是“线程池怎么创建”,而是默认参数把很多边界隐藏掉了。

图里最需要关注的是两个边界:队列有没有上限,线程数有没有上限。这两个边界如果没有控制住,任务高峰期就很容易从“异步处理”变成“异步堆积”。

《阿里巴巴 Java 开发手册》中有一条比较常见的规范:

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式创建。

这句话很多人都背过,但不一定真正理解它的问题在哪里。

我们先看一个最常见的FixedThreadPool

java

ExecutorService executor = Executors.newFixedThreadPool(10);

从使用上看,它创建了一个固定大小为 10 的线程池,好像挺安全的,因为线程数固定了,不会无限创建线程。

但问题不在线程数,而在队列。

newFixedThreadPool的源码如下:

java

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }

注意最后一行:

java

new LinkedBlockingQueue<Runnable>()

LinkedBlockingQueue如果不指定容量,默认容量是:

java

Integer.MAX_VALUE

也就是说,这个队列基本上可以认为是无界队列。

如果线程池里有 10 个线程,某一段时间内任务突然变多,那么前 10 个任务会被线程执行,后面的任务就会一直进入队列。因为队列几乎没有上限,所以线程池不会拒绝任务,任务只会越堆越多。

如果任务生产速度一直大于消费速度,最后占用的就是堆内存,严重时就会导致 OOM。

所以FixedThreadPool最大的问题不是“线程数固定”,而是“队列没限制”。

这也是为什么生产环境里一般要求使用ThreadPoolExecutor显式创建线程池,把核心线程数、最大线程数、队列大小、线程工厂、拒绝策略都写清楚。

几种内置线程池的问题

Executors里提供了几种常见线程池:

  1. FixedThreadPool
  2. SingleThreadExecutor
  3. CachedThreadPool
  4. ScheduledThreadPool

它们不是完全不能用,而是不适合在生产代码里不加控制地直接用。我们分别来看一下。

FixedThreadPool

前面已经看过它的源码:

java

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }

它的参数相当于:

  1. 核心线程数等于最大线程数
  2. 线程数固定
  3. 使用无界LinkedBlockingQueue

因为队列是无界的,所以当核心线程都在忙时,后续任务只会一直排队,不会触发扩容,也不容易触发拒绝策略。

很多人以为固定线程池比较稳,其实它只是把压力藏到了队列里。队列没满之前,系统看起来都还正常;等到内存撑不住时,问题就已经比较严重了。

SingleThreadExecutor

SingleThreadExecutor的源码也很类似:

java

public static ExecutorService newSingleThreadExecutor() { return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }

它只有一个工作线程,后面的任务都会排队串行执行。

如果只是少量后台任务,问题不明显。但如果任务提交速度很快,而这个单线程消费不过来,任务还是会一直堆到无界队列里。

所以它的问题和FixedThreadPool一样,只是更隐蔽,因为大家看到“单线程”时,会觉得它更可控。

实际上线程数是可控了,队列还是不可控。

CachedThreadPool

再看CachedThreadPool

java

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }

这个线程池的特点是:

  1. 核心线程数为 0
  2. 最大线程数是Integer.MAX_VALUE
  3. 使用SynchronousQueue
  4. 空闲线程 60 秒后回收

SynchronousQueue比较特殊,它不存任务。提交任务时,必须马上有线程接收;如果没有空闲线程,就会创建新线程。

这就带来一个问题:如果任务提交很快,任务执行又比较慢,线程池就会不断创建新线程。

线程并不是免费的。线程多了以后,会带来线程栈内存占用,也会带来大量上下文切换。严重时 CPU 会被切换消耗拖住,内存也可能被打满。

所以CachedThreadPool的风险不在队列,而在线程数几乎没有上限。

ScheduledThreadPool

ScheduledThreadPool一般用来执行延迟任务或者周期任务。

它底层使用的是延迟队列,队列本身也没有一个业务意义上的容量限制。如果定时任务提交过多,或者任务执行时间超过了调度周期,也会出现任务堆积。

比如一个任务每 1 秒调度一次,但每次执行需要 5 秒,如果没有控制好,就容易产生积压。

所以定时任务线程池也不能只关注线程数,还要关注任务是否堆积、任务执行耗时是否超过周期。

ThreadPoolExecutor 的几个核心参数

理解ThreadPoolExecutor的参数之前,最好先把任务提交后的执行顺序搞清楚。很多线程池问题,都是因为误以为“最大线程数会马上生效”。

这张图的关键点是:核心线程满了以后,任务会先进入队列;只有队列也满了,才会继续创建非核心线程。所以队列类型和队列容量,会直接影响maximumPoolSize是否有机会发挥作用。

既然不建议直接使用Executors,那我们就要自己创建ThreadPoolExecutor

它常用的构造方法如下:

java

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

这些参数不是随便填的,线程池在高峰期怎么表现,基本都由它们决定。

corePoolSize

corePoolSize表示核心线程数。

当任务提交到线程池时,如果当前线程数还没有达到corePoolSize,线程池会创建新线程来执行任务。

如果当前线程数已经达到corePoolSize,任务就会进入队列等待。

所以核心线程数太小,任务容易排队;核心线程数太大,又会造成线程资源浪费,甚至带来更多上下文切换。

一般会根据任务类型来估算一个初始值。

如果是 CPU 密集型任务,比如计算、加密、压缩等,线程数通常可以设置为:

text

CPU 核心数 + 1

如果是 IO 密集型任务,比如访问数据库、Redis、RPC 接口、文件、网络等,线程经常处于等待状态,线程数可以适当多一些。

常见估算公式是:

text

线程数 = CPU 核心数 * (1 + IO 耗时 / CPU 耗时)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/9 8:25:28

北京华恒智信为文旅演艺行业破解演艺人才职业生命周期短发展困局

在文旅演艺行业中&#xff0c;舞蹈演员始终面临固化的职业困境&#xff0c;行业默认演员职业终点是“跳到跳不动为止”&#xff0c;却从未明确从业者退役后的职业发展路径。无数文旅演艺从业者耗费青春投身行业&#xff0c;最终大多因身体机能衰退被迫离场&#xff0c;陷入职业…

作者头像 李华
网站建设 2026/7/9 8:17:47

基于SpringBoot的拍卖系统

一、关键词SpringBoot拍卖系统、竞拍商品、在线出价、订单管理、后台管理二、作品包含源码数据库万字设计文档全套环境和工具资源本地部署教程三、项目技术前端技术&#xff1a; Html、Css、Js、Vue3.2、Element-Plus后端技术&#xff1a;Java、SpringBoot3.3.0、MyBatis-Plus四…

作者头像 李华
网站建设 2026/7/9 8:15:32

鸿蒙新特性:Rating 星级评分组件——构建电影影单与评分统计

评分是用户表达喜好最直观的方式。从豆瓣电影的五星评分到 App Store 的五星评价&#xff0c;从外卖平台的服务打分到电商平台的商品评价——星级评分几乎覆盖了所有需要"表达满意程度"的场景。它比"喜欢/不喜欢"的二元选择更精确&#xff0c;又比"输…

作者头像 李华
网站建设 2026/7/9 8:14:55

餐饮门店会员系统怎么选?储值、积分和优惠券玩法分析

餐饮门店会员系统怎么选&#xff1f;储值、积分和优惠券玩法分析餐饮门店会员系统怎么选&#xff0c;关键不是能不能录入会员&#xff0c;而是能不能让顾客下次还来。餐饮消费频次高、决策快&#xff0c;会员玩法如果只是登记手机号&#xff0c;价值很有限。真正有用的会员系统…

作者头像 李华
网站建设 2026/7/9 8:09:18

UE4SS终极指南:5大核心功能解锁虚幻引擎游戏修改新境界

UE4SS终极指南&#xff1a;5大核心功能解锁虚幻引擎游戏修改新境界 【免费下载链接】RE-UE4SS Injectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games 项目地址: https://gitcode.com/gh_mirrors/re/RE-UE4SS …

作者头像 李华
网站建设 2026/7/9 8:07:29

华为云DWS磁盘使用率超90%后集群只读

一、故障现象 凌晨作业调度时段&#xff0c;ETL任务连续报错&#xff1a; 二、根因分析 DWS集群规则&#xff1a;单数据盘使用率 ≥ 90% 时&#xff0c;集群自动进入只读模式&#xff0c;default_transaction_read_only 被强制打开&#xff0c;所有 INSERT/UPDATE/DELETE/DDL…

作者头像 李华