拒绝策略是系统过载时的最后一道防线,仅决定多余任务的处理方式,而非解决流量过多问题;真正有效的流控需从任务提交源头设计,拒绝策略仅为兜底。

拒绝策略不是用来“扛流量”的,而是系统过载时的最后一道防线。它不解决任务太多的问题,只决定“多出来的任务怎么收场”。真正有效的流控,得从任务提交源头开始设计,拒绝策略只是兜底动作。
先确认是不是真过载
很多所谓“线程池满了”,其实是任务在队列里堆积,还没触发拒绝逻辑。尤其用 LinkedBlockingQueue 且没设容量上限时,队列能撑到内存耗尽,根本不会走拒绝流程。要判断真实状态,得看两个值:
-
活跃线程数:调用
getActiveCount(),是否已达maximumPoolSize -
队列当前大小:调用
getQueue().size(),是否等于队列容量(如ArrayBlockingQueue的构造参数)
只有这两个条件同时满足,新任务提交才会触发拒绝策略。否则,任务还在排队或扩容中。
四种内置策略怎么选
每种策略对应不同业务诉求,不能凭感觉换:
-
AbortPolicy(默认):抛
RejectedExecutionException,适合需要快速暴露问题的场景,比如测试环境或强一致性任务。调用方必须捕获异常并处理,否则会中断主线程 - CallerRunsPolicy:让提交线程自己执行任务,天然形成反压,降低提交速率。但若提交方是 Web 请求线程,会导致响应延迟甚至超时,慎用于高并发接口
- DiscardPolicy:静默丢弃,零开销。适用于日志、埋点等可丢失任务,但必须配套监控,否则丢了多少完全不可知
-
DiscardOldestPolicy:丢队列最老的任务,再重试提交当前任务。适合时效性要求高的场景,但要注意队列是否支持
poll()(PriorityBlockingQueue可能丢错优先级任务)
自定义策略的三个铁律
写自定义拒绝策略容易踩坑,关键就三点:
- 轻量:拒绝逻辑必须快,不能打日志聚合、不能同步发告警、不能操作共享集合,否则会阻塞所有后续任务提交
- 异步:告警、指标上报、消息落库等重操作,必须扔进独立线程池或消息队列,和拒绝路径解耦
-
可观测:至少记录任务标识、队列大小、当前线程池规模;建议用
AtomicLong统计拒绝次数,并通过 Micrometer 暴露为 Prometheus 指标
比换策略更重要的事
拒绝策略再好,也只是事后补救。可持续的流控得靠前端控制:
- 用 RateLimiter 或 Semaphore 在入口限速,比等线程池背压更可控
- 对慢依赖(如远程调用、DB 查询)加超时和熔断,避免单个慢任务拖垮整个池
- 拆分大任务,减少单任务执行时间,提升线程周转率
- 监控驱动调优:基于实际
getQueue().size()和getActiveCount()数据,动态调整核心线程数、队列容量或最大线程数
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











