问:
如果同时发出了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">
<bean
class="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线程)同步执行这些任务,导致调用方阻塞。 -
空闲状态的额外存活时间(注2)
keepAliveSeconds = 60秒
关键指标总结
- 并发执行线程数: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个订单?老板(调用线程)只能亲自下厨,结果顾客(用户)等更久了。
关键点:
- 这种策略能保证任务不丢失,但会拖慢提交任务的线程(比如网页响应变慢)。
- 适合对任务完整性要求高,但能接受短暂延迟的场景
注2:什么是keepAliveSeconds?
keepAliveSeconds
是Spring线程池ThreadPoolTaskExecutor
中的一个重要参数,用于指定线程池中非核心线程(超出corePoolSize
的线程)在空闲状态下的存活时间。具体含义如下:
-
作用机制:当线程池中的线程数量超过
corePoolSize
时,这些额外创建的线程在完成任务后,如果空闲时间达到keepAliveSeconds
设定的值(单位为秒),则会被销毁,直到线程数回落到corePoolSize
。 -
默认行为:若不显式设置此参数,默认值为60秒。通过调整此值可以优化线程资源利用率,避免长期空闲线程占用系统资源。
-
应用场景:适用于突发流量场景,例如当系统负载降低时,自动回收多余线程以节省资源;负载升高时又能动态创建新线程处理任务。
-
注意事项:该参数仅对非核心线程生效,核心线程(
corePoolSize
范围内的线程)默认会一直存活,除非显式设置allowCoreThreadTimeout
为true