大家好,我是晚安code。
Java线程池这篇,我拖到今天才敢写——不是它难,是它太"讲道理"了。网上讲线程池的文章一抓一大把,但大多上来就甩一张参数表,把人看懵。这篇我换种讲法:从"为什么要池化"讲起,用银行柜台把 7 大参数串起来,最后落到 CPU 密集/IO 密集线程数怎么配。点个收藏,我们开始。
一、线程池就一句话:事先备好,用完归还
程序跑起来,本质就是在占系统资源。其中最烧钱的,就是反复创建、销毁线程——每来一个任务就 new 一个线程,用完就扔,高峰期能把机器内存和上下文切换时间全吃光。线程池解决的,正是这个浪费。
池化技术(Pooling):一种资源管理思想——事先准备好一批资源放在"池子"里,有人要用就来拿,用完再还回去,避免反复创建和销毁。你可以理解为「公共洗衣房,机器随取随用,用完整理好放回」。
线程池(Thread Pool):池化技术在并发上的落地,提前创建一批线程复用,同时控制最大并发数、统一管理线程。创建、调度、回收这些脏活累活,它全包了。
好处就三条:降低资源消耗(线程复用,不反复建)、提高响应速度(线程现成,来了就能干活)、方便管理(并发数可控,出问题好排查)。

二、先别背参数:Executors 三大方法
Java 里创建线程池最省事的入口是 Executors 工具类,就三个常用方法(2026 年 8 月基于 JDK 8+ 验证):
// 单线程池:串行执行,像只有一个柜员的银行
ExecutorService pool1 = Executors.newSingleThreadExecutor();
// 固定大小:始终只有 5 个线程,超出进队列排队
ExecutorService pool2 = Executors.newFixedThreadPool(5);
// 可伸缩:遇强则强、遇弱则弱,没人用时线程自动回收
ExecutorService pool3 = Executors.newCachedThreadPool();
问题来了:这三个方法看着简单,生产环境却不建议用——因为它们把 7 大参数全藏起来了。newFixedThreadPool 的队列默认容量是 Integer.MAX_VALUE,请求一多任务全堆进队列,内存直接被撑爆 OOM;newCachedThreadPool 的线程数上限同样是 Integer.MAX_VALUE,并发一冲线程多到飞起,一样 OOM。阿里 Java 开发手册干脆把它写进了强制规范:线程池不允许用 Executors 创建。
我第一次跑出这种 OOM 时,就是这种表情。

可能有人会问:Executors 是不是完全不能用?
不至于。本地 demo、低并发的内部工具,图省事用它没问题;但凡接口要扛线上流量,老老实实手动配 ThreadPoolExecutor,就这。
三、手动创建线程池:7 大参数逐个拆
手动创建线程池,用的就是 ThreadPoolExecutor。它构造器里有 7 个参数,每一个都有明确含义:
ThreadPoolExecutor:JDK 并发包里最核心的线程池实现类,Java 里所有线程池创建方式最终都落在它身上。你可以理解为「线程池的总阀门」。
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 存活时间的单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler) // 拒绝策略
这 7 个参数看着抽象,用一个场景就全活了——银行办业务:
- 核心线程数 = 常驻柜台窗口,开门就上班
- 最大线程数 = 最多能开几个窗口(含临时加开的)
- 阻塞队列 = 候客区,窗口全忙时进来排队
- 空闲存活时间 = 临时窗口闲太久就撤掉
- 线程工厂 = 给线程起名、定属性,一般不用动
- 拒绝策略 = 大堂都满了,再来人怎么办
阻塞队列(BlockingQueue):线程池用来暂存"来不及处理"的任务的队列,窗口全忙时任务先在这儿排队,队列满了才走后面的分支。你可以理解为银行的「候客区」。

来看任务提交后的完整流转(图 1):

流程就一句:核心线程忙 → 进队列排队 → 队列满且线程数没到上限 → 开临时线程 → 全满了 → 走拒绝策略。
现在动手配一个,数字故意调小,方便看清每一段:
ExecutorService pool = new ThreadPoolExecutor(
2, // 核心线程 2 个
5, // 最大线程 5 个
3, TimeUnit.SECONDS, // 空闲 3 秒回收
new ArrayBlockingQueue<>(3), // 队列容量 3
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 最大承载 = 队列 3 + 最大线程 5 = 8
// 提交 9 个任务,第 9 个会触发拒绝策略
for (int i = 1; i <= 9; i++) {
pool.execute(() -> System.out.println(Thread.currentThread().getName() + " OK"));
}
pool.shutdown();
我第一次跑这段时,盯着"线程名 + OK"看了好一会儿才看懂:前 2 个任务走核心线程,第 3~5 个进队列排队,第 6~8 个开临时线程,第 9 个直接被拒。
四、线程池满了怎么办:4 种拒绝策略
拒绝策略不是越狠越好,选错了,会在最忙的时候悄悄丢任务。
拒绝策略(RejectedExecutionHandler):线程池满载(队列和最大线程都到顶)时,对"加塞不进来的任务"的处理方案。你可以理解为银行的「满员处理流程」。
四种策略,两种"硬",两种"软":
- AbortPolicy(默认):直接抛
RejectedExecutionException,任务丢了,但你会立刻知道 - CallerRunsPolicy:谁提交的,谁自己拿回去跑,不丢任务,但提交方会被拖慢
- DiscardPolicy:静默丢弃新任务,不抛异常,最危险——丢了没声音
- DiscardOldestPolicy:把队列里排最久的那条挤掉,让新任务进来
| 策略 | 行为 | 会丢任务吗 | 适合场景 |
|---|---|---|---|
| AbortPolicy | 直接抛异常 | 是 | 默认;宁可报错也不静默丢 |
| CallerRunsPolicy | 提交方自己跑 | 否 | 不想丢任务、能接受变慢 |
| DiscardPolicy | 静默丢新任务 | 是 | 能容忍丢,但尽量别用 |
| DiscardOldestPolicy | 丢最老的任务 | 是 | 任务越新越重要时 |
我最开始以为"不抛异常就是好的",其实恰恰相反——静默丢弃会在线上最忙的时候悄悄丢单,等对账才发现。想到这个场景,血压就上来了。


选策略的核心就一条:能不能接受丢任务,以及丢了要不要有声音。求稳选 CallerRunsPolicy,宁可慢不丢单;省事就接受默认的 AbortPolicy,让异常替你把问题喊出来。
五、最大线程数设多少:CPU 密集 vs IO 密集
最后一个高频问题:核心线程数和最大线程数到底填多少?没有标准答案,但判断标准就一条——看任务是 CPU 吃得多,还是 IO 等得多。
- CPU 密集型:任务在拼命算,线程数设成 CPU 核数最合适,多了反而来回切换更慢
- IO 密集型:任务一大半时间在等(等网络、等磁盘),可以让线程数翻倍,用"等"的时间多干活
// 拿到本机 CPU 核数,它是 CPU 密集型的参考基准
int cores = Runtime.getRuntime().availableProcessors();
// CPU 密集型:核心线程数 ≈ cores
// IO 密集型:核心线程数 ≈ cores * 2 起,压测再微调
| 类型 | 特点 | 线程数建议 |
|---|---|---|
| CPU 密集 | 计算为主,几乎不等待 | 核数 cores |
| IO 密集 | 等网络/磁盘为主 | cores * 2 左右起,再实测调 |
IO 密集这个"乘 2"只是起点。我今天的笔记里就写了:程序里 15 个大型任务一大半都在等 IO,线程数就该按"等待时间 / 干活时间"的比例往上加,配完用压测说话。没有标准答案,只能自己试。

好了,Java线程池这一块算是啃完了。它不神秘,就是"事先备好、用完归还"八个字,外加 7 大参数这一整套"银行系统"。生产环境记住一条:别用 Executors,自己配 ThreadPoolExecutor——参数看得见,心里才有底。
可能有人会问:线程池用完一定要 shutdown 吗?
要。非守护线程不关闭,JVM 就不会退出,进程会一直挂着。程序结束前记得
shutdown(),不然会莫名其妙发现进程杀不掉。
参考与说明
- 本文在 Java线程池主题上结合个人学习笔记独立撰写。
- 涉及 JDK 源码与参数以官方文档为准;示例代码基于 JDK 8+(2026 年 8 月验证)。
我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你用 Executors 还是手动配 ThreadPoolExecutor?配线程池踩过哪些坑?