生产线程池疑问
生产最近出现一些mq堆积事情,分析代码发现mq消费内部还使用了spring内置线程池。
但生产上只配置了spring.task.execution.thread.pool.core-size=16,原以为核心线程数是16,但实际日志发现只到task-8,然后加日志打印线程池相关属性
if (configNacos.getBoolean("thread.log.debug", true)) { log.info("活跃线程:{}, 池大小:{},coresize:{} queue:{} remaincapi{}", threadPoolExecutor.getActiveCount(), threadPoolExecutor.getPoolSize(),threadPoolExecutor.getCorePoolSize(), threadPoolExecutor.getQueue().size(), threadPoolExecutor.getQueue().remainingCapacity()); }生产日志才发现coresize是8(后面说明原因),队列是无限大的(Integer.MAX_VALUE),居然一直在生产跑着...幸好也没发生OOM,任务基本都执行了。
活跃线程:7, 池大小:8,coresize:8 queue:0 remaincapi2147483647 ----生产一条日志示例
从日志看当前等待队列的线程为0个,剩余队列容量是 2147483647恰好就是Integer.MAX_VALUE。说明队列是无限大的。需要补充最大线程数,队列数和拒绝策略的配置
线程池配置
# 核心线程数(保持 16) spring.task.execution.pool.core-size=16 # 最大线程数(建议设置为一个合理的数字,比如 CPU 核数的 2-4 倍,或根据业务压测结果设定),如果不设置就是INT.MAX_VALUE spring.task.execution.pool.max-size=50 # 任务队列容量(建议设置一个合理的上限,防止无限制积压),如果不设置就是INT.MAX_VALUE spring.task.execution.pool.queue-capacity=200 # 线程名前缀(方便排查) spring.task.execution.thread-name-prefix=sign-task- # 拒绝策略 (当队列满了的时候新任务采取什么策略,具体后面列出) spring.task.execution.pool.rejection-policy=CallerRunsPolicy # 线程空闲多久被回收(默认60秒) spring.task.execution.pool.keep-alive=60但核心线程数这个生产设置的时候写错属性值了,
spring.task.execution.thread.pool.core-size=16 这个是错误的,不生效,使用了spring默认的核心线程数是8!
根据 ThreadPoolExecutor 的工作规则,只有当任务队列满了之后,才会创建超出核心线程数(默认8)的新线程。而未配置队列容量的,队列容量几乎是无限的,所以:任务会无限堆积:当 8 个核心线程都被占满后,所有新来的任务都会成功进入这个无边界的队列等待,既不会创建新线程,也不会触发任何拒绝策略。内存被耗尽:这个队列会像一个无底洞,不断吞噬提交过来的任务。随着任务越积越多,内存占用持续飙升,最终导致 OutOfMemoryError,应用崩溃。
拒绝策略
为什么会触发拒绝策略?
触发拒绝策略,意味着你的线程池已经达到最大承载能力:
当线程池中的线程数达到
max-size,并且任务队列 (queue-capacity) 也已满,此时再有新任务提交,就会触发拒绝策略。
因此,为了能让拒绝策略生效,你需要先为max-size和queue-capacity设置一个具体、合理的数值。否则,如果队列容量是无限大(默认),任务只会无限堆积而永远不会触发拒绝策略,最终导致内存溢出(OOM)。
如何选择?
对于大多数业务系统,CallerRunsPolicy是兼顾安全与稳定的首选。它确保了任务不会丢失,同时通过让调用线程“慢下来”的方式,反向保护了整个应用。选择AbortPolicy则更适合那些需要你主动捕获异常并执行降级逻辑的场景
线程回收
业务高峰时,可能线程数创建到了max-size,当业务低峰时,大部分线程可能都是空闲的,就会被回收。
当线程数超过核心线程数(core-size)后,多余的空闲线程是会被回收的,最终线程池会收缩回你设置的核心线程数。这个“回收”行为,是由keep-alive(默认60秒)这个配置项控制的