面试官狂喜!我用这 5 分钟讲清了 ThreadPoolExecutor 饱和策略,逆袭上岸



社招面试现场:线程池饱和策略?额......

事情发生在上个月。

那天我去面了一家很喜欢的大厂,技术氛围一流,薪资也令人心动。前两轮发挥还不错,第三轮是一位老哥来的,一开场就丢出个问题:

"你熟悉 Java 线程池吗?那你说说 ThreadPoolExecutor 的饱和策略有哪些?"

我一愣。

线程池熟啊,日常用得不能更熟。但"饱和策略"这四个字,把我给问住了。脑袋里只冒出几个关键词:"拒绝策略"、"AbortPolicy"、"CallerRunsPolicy",但我说得磕磕巴巴,还举不出例子。

面试官点点头:"好,继续聊其他的。"

我心里咯噔一下,知道大概凉了。

回家路上我打开 IDEA,撸了一遍 ThreadPoolExecutor 的源码,一边骂自己怎么把这么重要的一块知识点给漏了。

今天,我就来给你讲清楚这个"容易被忽视但很关键"的 Java 面试题------线程池饱和策略(RejectedExecutionHandler)

为什么会有饱和策略?

我们都知道,Java 的线程池 ThreadPoolExecutor 是用来管理线程的工具。你提交任务,它就安排线程去执行,能复用线程、减少线程创建销毁的开销,非常高效。

但是......线程池也有上限啊!

一个线程池通常有这些配置:

当你疯狂地往线程池里提交任务,如果满足下面三个条件:

  • 核心线程都在忙(corePoolSize都被用光);
  • 任务队列满了(queue也塞不进新任务);
  • 最大线程数也到了上限(maximumPoolSize不再增长);

这时候,再有新任务进来怎么办?

是的,饱和策略就登场了!

Java 提供了四种默认的饱和策略,你也可以自定义。

四大默认饱和策略,背会也要理解!

ThreadPoolExecutor 中默认提供了四种实现 RejectedExecutionHandler 接口的策略,分别是:

1. AbortPolicy(默认)

策略说明:直接抛出异常

当线程池满了,再提交任务就会抛出 RejectedExecutionException 异常。

这其实是最常见的"拒绝"方式,适合不容错的系统,比如交易系统,出错宁可终止也不能乱来。

适用场景: 高可靠、任务不能丢的系统。

2. CallerRunsPolicy

策略说明:把任务交给调用者线程执行

如果线程池满了,就不让线程池处理了,而是让提交任务的线程(比如 main 线程)自己去执行这个任务

这么做的好处是让提交任务的速度降下来,从而让线程池有时间喘息。

适用场景: 对任务处理不是特别紧急,允许降速的系统。

3. DiscardPolicy

策略说明:直接丢弃任务,不抛异常

听起来有点激进,但有些系统确实能容忍这种"最好做,做不了也无所谓"的态度。

使用时,线程池会默默地把任务丢掉,连异常都不报,简直是"佛系线程池"。

适用场景: 对任务处理精度要求不高、可丢失的日志处理、指标上报系统。

4. DiscardOldestPolicy

策略说明:丢掉队列里最老的任务,然后尝试执行当前任务

注意,它不是丢弃新任务,而是"挤走"队列最前面的任务,替换成当前这个任务。换句话说,它在一定程度上"优待了新来的"。

适用场景: 某些对实时性要求高、任务可以丢但必须处理当前任务的系统。

举个栗子,真的"饱和"你看看!

我们来模拟一个线程池"饱和"的现场,用 2 个线程,任务队列容量是 1,提交 4 个任务,看看策略如何运作:

你可以尝试把 CallerRunsPolicy 换成其他几种策略再跑一遍,会看到不同的执行效果!

自定义策略,玩出花来!

除了 Java 提供的四种默认策略,其实你完全可以实现自己的策略,比如:

  • 写入日志记录被拒绝的任务;
  • 把任务持久化到消息队列;
  • 甚至可以报警提示线程池处理能力不足。

你只需要把这个 handler 配到 ThreadPoolExecutor 构造函数里即可。

真实案例:我们系统差点"雪崩"

曾经我们系统有一个文件上传转码服务,里面使用了线程池处理上传文件。

某天突然访问量飙升,线程池被打爆,队列满、线程也满,但我们配置的是默认的 AbortPolicy,于是系统开始疯狂抛 RejectedExecutionException,还没加报警,一时之间我们完全不知道有大量文件被拒绝处理......

后来我们做了两件事:

  • 把饱和策略改为 CallerRunsPolicy,让上传线程自己慢慢处理任务;
  • 对关键操作做了降级保护,超过阈值就返回"稍后上传"提示。

再后来,我们封装了自己的线程池工具类,对业务线程池进行统一管理和监控。

面试怎么答才加分?

面试官提到饱和策略时,你可以这样回答:

ThreadPoolExecutor 提供了 4 种默认饱和策略,包括:

AbortPolicy:抛异常(默认);

CallerRunsPolicy:交给提交线程执行;

DiscardPolicy:直接丢弃;

DiscardOldestPolicy:丢掉最旧的任务,执行当前的。

如果业务有特殊需求,也可以自定义 RejectedExecutionHandler,比如将任务转存到MQ或落库。

实际使用时,我们会根据任务的重要性、实时性、容错性来选择合适的策略。

最好还能结合你在项目中怎么用的,说出一个场景,比如"我们系统为了防止雪崩用了CallerRunsPolicy,并在业务层做了限流"------稳稳拿捏!

总结一下!

线程池很强大,但你得知道它的极限在哪里。

当线程池处理不过来了,任务还在飞快地来,这时候它会"饱和"------你要知道它会怎么"拒绝"。

这就是 RejectedExecutionHandler 的职责!你可以选择抛异常、丢任务、让自己执行、甚至把最老的赶走,也可以......自定义规则。

别等出问题时才想起它------像我一样就晚了。

END

如果你看完这篇文章有点收获,欢迎:

  • 点个 "赞" 让我知道你也关注 Java 基础;
  • 转发给正在面试的朋友;
  • 关注我,小米,一个还在努力卷进大厂的31岁技术人,下期我们聊聊 线程池中的阻塞队列选型 怎么选~

我们下次见~~

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号"软件求生",获取更多技术干货!

相关推荐
书源丶6 分钟前
三十六、File 类与 IO 流基础——文件操作的「第一步」
java
刀法如飞12 分钟前
Go数组去重的20种实现方式,AI时代解决问题的不同思路
后端·算法·go
AI人工智能+电脑小能手39 分钟前
【大白话说Java面试题】【Java基础篇】第30题:JDK动态代理和CGLIB动态代理有什么区别
java·开发语言·后端·面试·代理模式
swipe1 小时前
别再把 AI 聊天做成纯文本:从 agui 这个前后端项目,拆解“可感知工具调用”的流式 AI UI
后端·langchain·llm
GetcharZp1 小时前
GitHub 爆火!纯 Go 编写的文件同步神器 Syncthing,凭什么成为程序员的标配?
后端
hERS EOUS1 小时前
SpringBoot 使用 spring.profiles.active 来区分不同环境配置
spring boot·后端·spring
DFT计算杂谈1 小时前
wannier90 参数详解大全
java·前端·css·html·css3
LucianaiB1 小时前
我用飞书多维表做了一个 AI 活动推荐智能体:每天自动催我别错过截止日期!
后端
marsh02061 小时前
43 openclaw熔断与降级:保障系统在异常情况下的可用性
java·运维·网络·ai·编程·技术
张健11564096481 小时前
临界区和同一线程上锁
java·开发语言·jvm