请求超过Spring线程池的最大线程(处理逻辑)
问:
如果同时发出了100个异步请求,超过了能处理的最大线程,线程池的工作机制是怎么样的呢?
线程池大小:
<bean id="taskExecutor"class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><property name="corePoolSize" value="5" /><property name="maxPoolSize" value="10" /><property name="queueCapacity" value="50" /><property name="keepAliveSeconds" value="60" /><property name="threadNamePrefix" value="ssh-thread-" /><property name="rejectedExecutionHandler"><beanclass="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /></property></bean><task:annotation-driven executor="taskExecutor"/>
核心线程处理
前5个请求会立即由核心线程(corePoolSize=5)执行,此时活跃线程数达到核心线程数上限。队列缓冲
接下来的50个请求(queueCapacity=50)会被放入工作队列等待执行,此时队列满负荷。扩展线程处理
当队列满后,线程池会创建新线程处理后续请求,直到线程总数达到maxPoolSize=10。此时会再处理5个请求(10-5=5),总处理量为:5(核心) + 5(扩展) = 10个并发执行任务。拒绝策略触发(注1)
剩余40个请求(100-5-50-5)将触发CallerRunsPolicy策略,由提交任务的调用线程(如HTTP请求的Tomcat线程)同步执行这些任务,导致调用方阻塞。
关键指标总结
- 并发执行线程数:10(maxPoolSize)
- 队列堆积任务数:50(queueCapacity)
- 同步降级任务数:40(CallerRunsPolicy处理)
- 线程空闲回收:非核心线程60秒(keepAliveSeconds)后自动销毁。
注1:什么是拒绝策略
通俗解释线程池的CallerRunsPolicy
策略
当线程池(比如你配置的5核心线程+10最大线程+50队列)被100个任务同时塞满时,会发生以下情况:
- 前5个任务:直接由5个核心线程处理(像5个固定工人开工)。
- 接下来的50个任务:排队等待(像把任务扔进一个能装50个任务的篮子)。
- 再接下来的5个任务:临时再招5个工人(总工人数达到10个上限)。
- 最后40个任务:
- 因为工人和篮子都满了,线程池会喊:“谁提交的任务谁自己干!”
- 比如Tomcat线程(处理HTTP请求的线程)会被迫停下当前工作,直接去执行这40个任务,导致网页响应变慢甚至卡死。
类比:
想象餐厅只有10个厨师(线程),50个待做菜的订单(队列)。突然来了100个订单:
前10个厨师先做10个菜(5个固定厨师+5个临时工)。
50个订单排队等。
剩下的40个订单?老板(调用线程)只能亲自下厨,结果顾客(用户)等更久了。
关键点:
- 这种策略能保证任务不丢失,但会拖慢提交任务的线程(比如网页响应变慢)。
- 适合对任务完整性要求高,但能接受短暂延迟的场景