自定义拒绝策略中应通过原子快照记录线程池状态指纹,包括active、poolSize、queueSize等字段,生成固定KV格式字符串;WARN级默认采样输出,禁用JSON、迭代队列、二次调用状态方法及额外任务提交。

在自定义拒绝策略中记录线程池状态指纹,关键不是“打日志”,而是以可追溯、可比对、低干扰的方式捕获那一刻的快照。重点在于:获取的是拒绝发生瞬间的真实状态,而非调用日志时可能已变化的状态。
用原子快照避免状态漂移
ThreadPoolExecutor 的 getActiveCount()、getQueue().remainingCapacity() 等方法本身是非原子的,且队列容量可能在并发操作中动态变化。必须在拒绝逻辑入口处一次性读取多个关键字段:
- 立即调用
executor.getActiveCount() - 立即调用
executor.getPoolSize()(当前线程数,含空闲) - 立即调用
executor.getQueue().size()和executor.getQueue().remainingCapacity()(注意:对有界队列才有效) - 记录
executor.getLargestPoolSize()和executor.getCompletedTaskCount()可辅助判断历史压力
封装为结构化指纹字符串
避免拼接易错、难解析的文本。推荐生成固定格式的 KV 字符串,例如:
pool=xxx,active=23,poolSize=25,queueSize=102,remainCap=478,largest=31,completed=1892
这种格式便于日志采集系统(如 ELK)自动提取字段,也方便 grep 或 Prometheus 指标埋点。不要用 JSON——拒绝策略中应极致轻量,避免对象创建和序列化开销。
控制日志粒度与采样
拒绝本身是异常信号,但高频拒绝时全量打日志会拖垮系统。建议:
- 默认只记录 WARN 级别 + 指纹字符串(不带堆栈)
- 对连续拒绝做滑动窗口计数,每 N 次拒绝才输出一次完整快照(如每 5 次记 1 次)
- 若需诊断,临时开启 DEBUG 级别并打印
Thread.currentThread().getName()和System.nanoTime()微秒级时间戳
避免常见陷阱
以下操作会破坏指纹真实性或引入风险:
- ❌ 在日志语句里多次调用
getActiveCount()—— 值可能已变 - ❌ 调用
getQueue().iterator()或遍历队列内容 —— 可能触发锁或 OOM(尤其大任务队列) - ❌ 在拒绝策略中提交新任务或唤醒线程 —— 违反拒绝语义,可能死锁
- ❌ 使用
Logback的%caller或 MDC 自动注入 —— 拒绝发生在 JVM 线程调度底层,上下文常为空或错乱










