咱们用「奶茶店经营」的比喻,无痛掌握线程池的核心知识点,再配上代码和真实场景例子,包你笑着学会!😎
一、线程池的「灵魂参数」——奶茶店员工手册📜
假设你开了个奶茶店(线程池),需要招人处理订单(任务)。为了高效运营,你定了几个规矩:
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 正式工数量(corePoolSize):生意再差也不能辞退
5, // 最大员工数(maximumPoolSize):旺季最多招5人
60, // 临时工存活时间(keepAliveTime):没活干60秒就辞退
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 排队区(workQueue):最多10个顾客排队
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:人满时直接赶客!
);
1. 核心参数详解
-
正式工(corePoolSize):即使没顾客(任务),也得留3个员工摸鱼,保证随时有人接单。
-
临时工(maximumPoolSize - corePoolSize):高峰期允许再雇2人(总5人),但干完活60秒没新单就辞退。
-
排队区(workQueue):店内最多容纳10个排队顾客(任务队列),超出后触发「拒绝策略」。
-
拒绝策略:人满时的应对措施——赶客、让顾客自己动手做奶茶、偷偷记下电话稍后联系等(下文细说)。
二、线程池「接单流程」——奶茶店营业日志📝
-
第1步:顾客A进店,正式工1号接单。
-
第2步:顾客B、C陆续进店,正式工2号、3号接单。
-
第3步:顾客D进店,发现正式工都在忙,去排队区等待(进入队列)。
-
第4步:顾客E到K(共10人)陆续进店,排满队列。
-
第5步:顾客L进店,队列已满,雇临时工1号接单。
-
第6步:顾客M进店,雇临时工2号接单(总员工5人)。
-
第7步:顾客N进店,人满触发拒绝策略(比如直接赶走)。
流程图:
新顾客 → 正式工有空? → 是:立刻接单
↓否
队列有空位? → 是:排队等待
↓否
还能雇临时工? → 是:临时工接单
↓否
执行拒绝策略
三、线程池的「四大拒绝策略」——人满时的骚操作🤯
当「排队区满」且「员工数达到上限」时,如何应对新顾客?
策略名 | 操作 | 现实场景类比 |
---|---|---|
AbortPolicy | 直接抛异常(RejectedExecutionException) | 挂出“今日售罄”牌子,不让新客进店 |
CallerRunsPolicy | 让提交任务的线程自己执行任务 | 老板亲自做奶茶:“你们别排队了,我来!” |
DiscardPolicy | 默默丢弃新任务,不通知 | 假装没看见新顾客,偷偷忽略 |
DiscardOldestPolicy | 丢弃队列中最老的任务,然后重试提交新任务 | 把等最久的顾客请走,换新客进来 |
自定义拒绝策略:
pool.setRejectedExecutionHandler((task, executor) -> {
log.warn("任务被拒绝,但记到数据库稍后重试!");
saveToDatabase(task); // 自定义处理逻辑
});
四、线程池的「类型」——不同奶茶店经营模式🍹
1. 固定员工店(FixedThreadPool)
Executors.newFixedThreadPool(3); // 只有3个正式工,无临时工,队列无限长
-
优点:稳定可控,不会突然雇人/裁员。
-
缺点:队列无限长可能引发内存溢出(OOM)。
-
场景:已知任务量稳定(如定时统计报表)。
2. 弹性用工店(CachedThreadPool)
Executors.newCachedThreadPool(); // 正式工=0,临时工无限多,队列不存任务
-
优点:来多少单招多少人,60秒没活就辞退。
-
缺点:任务暴涨时可能创建大量线程,导致系统崩溃。
-
场景:短时高频任务(如突发流量请求)。
3. 定时任务店(ScheduledThreadPool)
Executors.newScheduledThreadPool(2); // 支持定时/周期性任务
pool.schedule(() -> System.out.println("3秒后出餐"), 3, TimeUnit.SECONDS);
-
场景:定时发短信、每天凌晨统计数据。
4. 单线程店(SingleThreadExecutor)
Executors.newSingleThreadExecutor(); // 只有1个正式工,保证任务顺序执行
-
场景:需要顺序处理任务(如日志顺序写入)。
坑点提示:
-
慎用
Executors
默认工厂!推荐用ThreadPoolExecutor
自定义参数,避免队列无界导致OOM。
五、线程池的「调优与监控」——店长必备技能📊
1. 参数调优公式(参考)
-
CPU密集型任务(如计算):
核心线程数 = CPU核数 + 1
-
IO密集型任务(如网络请求):
核心线程数 = CPU核数 * 2
2. 监控关键指标
// 获取线程池状态
int activeCount = pool.getActiveCount(); // 正在接单的员工数
long completedTaskCount = pool.getCompletedTaskCount(); // 已完成任务数
int queueSize = pool.getQueue().size(); // 排队顾客数
-
推荐工具:Spring Boot Actuator、Prometheus + Grafana 监控线程池。
3. 动态调整参数(Java 21+)
ThreadPoolExecutor pool = ...;
pool.setCorePoolSize(5); // 旺季增加正式工
pool.setMaximumPoolSize(10); // 扩大临时工编制
六、真实场景案例
案例1:Web服务器请求处理
// 自定义线程池替代Tomcat默认
ThreadPoolExecutor httpExecutor = new ThreadPoolExecutor(
10, 50, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 处理HTTP请求时提交任务
httpExecutor.submit(() -> handleRequest(request));
-
效果:防止突发流量冲垮服务器,队列满时由调用线程处理(降级)。
案例2:批量文件处理
// 使用ForkJoinPool分治处理大任务
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
forkJoinPool.submit(() -> processFilesInParallel(files));
-
效果:大文件拆分成小任务并行处理,加快速度。
七、高频面试题速答💡
-
Q:线程池执行
submit()
和execute()
有什么区别?-
submit()
可接收Runnable
和Callable
,返回Future
;execute()
只接收Runnable
,无返回值。
-
-
Q:线程池为何要自定义
ThreadFactory
?-
可定制线程名称、优先级、是否为守护线程,便于监控和排查问题。
-
-
Q:线程池的
shutdown()
和shutdownNow()
区别?-
shutdown()
温和停止,等所有排队任务执行完;shutdownNow()
立刻停止,返回未执行的任务列表。
-
总结
线程池就像管理一家奶茶店:
-
参数配置是员工手册,决定了能接多少单。
-
拒绝策略是应急预案,避免忙时系统崩溃。
-
监控调优是数据分析,帮助持续提升性能。
记住:别让线程池成为系统瓶颈,合理配置才能笑对高并发! 🚀