拒绝策略必须纯内存、无阻塞、毫秒级完成,仅限简短日志、原子计数或告警标志;任何io操作都应异步化,由专用线程池处理,避免卡死主线程。

拒绝策略中执行高耗时 IO,会直接拖垮主线程甚至整个线程池——这不是“慢”,而是“卡死”。因为拒绝策略(如 RejectedExecutionHandler)是在提交任务失败时,由调用线程(即主业务线程)同步执行的。一旦它包含网络请求、日志刷盘、数据库写入等阻塞操作,该线程就会被钉住,无法返回,后续所有依赖它的逻辑(如 HTTP 响应、事务提交、定时调度)全部停滞。
拒绝策略必须是纯内存、无阻塞、毫秒级完成
它只适合做三件事:记录简短日志(如 log.debug("rejected: {}", task))、原子计数(counter.increment())、或设置告警标志。任何涉及 I/O、锁等待、远程调用、JSON 序列化(尤其大对象)的操作都属于危险行为。
- ❌ 错误示例:在
CallerRunsPolicy或自定义 handler 中调用sendToPrometheus(metrics)、writeToAuditLog(task)、notifySlack("rejected!") - ✅ 正确做法:仅存入内存队列(如
rejectQueue.offer(task)),或触发异步信号(如CompletableFuture.runAsync(() -> doRealIO(task)))
把 IO 拆出来,交给专用通道处理
拒绝不是终点,而是需要异步兜底的起点。主调用线程绝不应承担 IO 责任:
- 用
ConcurrentLinkedQueue或BlockingQueue缓存被拒任务元数据(ID、类型、时间戳),避免扩容锁争抢 - 启动一个独立的守护线程或固定大小的后台线程池(如
Executors.newSingleThreadScheduledExecutor()),定期拉取并批量执行 IO - 对 IO 操作加超时(如
HttpClient设置 500ms connect/read timeout)和降级开关(如熔断后跳过日志上报)
从源头减少拒绝发生,比事后补救更有效
高频拒绝本身已是系统失衡信号。需配合以下加固措施:
- 线程池配置匹配负载类型:I/O 密集型任务,
maxWorkers应显著高于 CPU 核心数(例如 4C 机器设为 32) - 使用有界队列 + 明确拒绝策略,避免无界队列吃光堆内存;禁用默认的
AbortPolicy(只抛异常不记录) - 在任务提交前做轻量预检:如检查当前队列长度 > 阈值时,直接返回 429 或走降级逻辑,不进线程池
- 监控指标联动:当
rejectedTaskCount突增,自动触发告警并临时扩容线程池(需配合动态参数支持)
本质上,拒绝策略是系统的“紧急出口”,不是“后勤中心”。让它保持窄、快、哑,IO 交给专业的人干,系统才不会因一次拒绝而集体窒息。











