Java线程池一篇讲透:Executors三大方法、7大参数、4种拒绝策略

简介: Java线程池是面试高频考点。本文用银行柜台比喻拆解三大方法、7大参数与4种拒绝策略,最后给出CPU/IO密集型线程数怎么设。

大家好,我是晚安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):

Java线程池执行任务流程图:核心线程、阻塞队列、最大线程数、拒绝策略四段判断

流程就一句:核心线程忙 → 进队列排队 → 队列满且线程数没到上限 → 开临时线程 → 全满了 → 走拒绝策略

现在动手配一个,数字故意调小,方便看清每一段:

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):线程池满载(队列和最大线程都到顶)时,对"加塞不进来的任务"的处理方案。你可以理解为银行的「满员处理流程」。

四种策略,两种"硬",两种"软":

  1. AbortPolicy(默认):直接抛 RejectedExecutionException,任务丢了,但你会立刻知道
  2. CallerRunsPolicy:谁提交的,谁自己拿回去跑,不丢任务,但提交方会被拖慢
  3. DiscardPolicy:静默丢弃新任务,不抛异常,最危险——丢了没声音
  4. DiscardOldestPolicy:把队列里排最久的那条挤掉,让新任务进来
策略 行为 会丢任务吗 适合场景
AbortPolicy 直接抛异常 默认;宁可报错也不静默丢
CallerRunsPolicy 提交方自己跑 不想丢任务、能接受变慢
DiscardPolicy 静默丢新任务 能容忍丢,但尽量别用
DiscardOldestPolicy 丢最老的任务 任务越新越重要时

我最开始以为"不抛异常就是好的",其实恰恰相反——静默丢弃会在线上最忙的时候悄悄丢单,等对账才发现。想到这个场景,血压就上来了。

Abort 吼一声、CallerRuns 自己扛、Discard 假装无事、Oldest 挤掉最老的

选策略的核心就一条:能不能接受丢任务,以及丢了要不要有声音。求稳选 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?配线程池踩过哪些坑?

目录
相关文章
|
3月前
|
前端开发 Java Nacos
Nacos 注解全解析:7 个核心注解 + 5 个生产踩坑清单(2026 实测)
Nacos 注解用错,是 90% 微服务 Bug 的根源。本文把注册发现、配置热更新的 7 个核心注解、动态刷新原理和 5 个生产踩坑一次讲透,附可运行代码。
502 2
Nacos 注解全解析:7 个核心注解 + 5 个生产踩坑清单(2026 实测)
|
9天前
|
机器学习/深度学习 缓存 人工智能
阿里云kimi-k3模型详解:模型能力、价格、上下文限制及使用注意事项参考
本文介绍了阿里云百炼平台提供的旗舰级大模型Kimi-K3。该模型由月之暗面研发,拥有2.8万亿参数,是全球首个开源的三万亿级模型,并创新性地采用了KDA混合线性注意力与注意力残差技术。它原生支持文本与图像输入,具备强制开启的深度思考模式,并提供高达100万tokens的上下文窗口。文章详细梳理了其五大区域(北京、新加坡、日本、德国、美国)的部署与功能矩阵,重点解读了其独有的动态加载工具DLT机制在优化智能体开发流程上的价值,以及适用于长程编程、超长文档分析、复杂推理等高阶场景的定位。同时,也明确了其定价策略与通过百炼平台进行OpenAI/DashScope协议兼容调用的方式。
|
6月前
|
算法 Java 关系型数据库
JVM GC 深度破局:G1 与 ZGC 底层原理、生产调优全链路实战
本文深度解析JDK17主流GC:G1(默认,兼顾吞吐与延迟)与ZGC(革命性低延迟,STW&lt;1ms)。涵盖核心理论(可达性分析、三色标记)、内存布局、全流程机制(SATB写屏障 vs 染色指针+读屏障)、关键参数调优及生产选型指南,助你精准定位性能瓶颈,高效优化JVM。
1312 5
|
22天前
|
缓存 人工智能 5G
DeepSeek 涨价后怎么办:峰谷错峰省一半,小米多模态模型可平替
DeepSeek 涨价 8 月 17 日生效,最高涨 1100%。看懂峰谷计价、错峰调用能省一半;预算还紧,小米 MiMo 多模态模型可以平替。
248 0
DeepSeek 涨价后怎么办:峰谷错峰省一半,小米多模态模型可平替
|
25天前
|
JSON 人工智能 API
Function Calling 会被 MCP 取代吗?理清两者关系与使用细节
Function Calling 会被 MCP 取代吗?不会。本文讲透它的调用机制与使用细节,说清两者分层关系:一个是机制,一个是协议。
171 0
Function Calling 会被 MCP 取代吗?理清两者关系与使用细节
|
27天前
|
缓存 人工智能 JSON
DeepSeek V4 Pro转正,性能逼近Fable 5,价格仅六十分之一
DeepSeek V4 Pro正式版发布:1M上下文、384K输出,价格只有Fable 5的六十分之一。本文从价格、能力、跑分可信度拆解,说清该不该切。
288 0
|
1月前
|
运维 Java 调度
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
单机 @Scheduled 扛不住分布式定时任务?拆解 XXL-JOB 调度中心与执行器架构、任务分片、失败重试机制,附 30 分钟接入示例。
240 0
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
|
2月前
|
自然语言处理 关系型数据库 MySQL
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战
适合想快速上手 Elasticsearch 的后端开发者和搜索服务初学者。读完你会理解 ES 为什么搜得快、怎么在本地搭一套完整环境、怎么写查询、以及怎么把 MySQL 数据同步到 ES。
526 2
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战
|
1月前
|
机器学习/深度学习 人工智能 缓存
DeepSeek V4 Flash 对标 Gemini 3.6,AI 大跑毒时代
DeepSeek V4 Flash 0731 上线公测,智能指数追平 Gemini 3.6 Flash,价格仅其零头,拆解「大跑毒时代」谁先出局。
382 0
DeepSeek V4 Flash 对标 Gemini 3.6,AI 大跑毒时代
|
2月前
|
算法 Java Nacos
Sentinel 流量治理实战:熔断降级限流 + Nacos 持久化
Sentinel 流量治理如何落地?本文从服务雪崩讲起,带你掌握熔断、降级、限流策略,配合 Nacos 实现规则持久化
404 0