线程池拒绝策略

Abstract

当线程池和工作队列都满时,新提交的任务会被拒绝,触发 RejectedExecutionHandler

四种内置策略

策略行为适用场景
AbortPolicy抛出 RejectedExecutionException默认需要快速失败的场景
CallerRunsPolicy提交任务的线程执行该任务非核心任务,允许降级
DiscardPolicy静默丢弃,不抛异常不重要的任务(如日志)
DiscardOldestPolicy丢弃队列中最旧的任务,然后重新提交实时性要求高的任务

策略详解

AbortPolicy(默认)

// 默认策略:线程池满时直接抛出异常
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 4, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100)
    // 默认就是 AbortPolicy,不指定也行
);

适用场景:任务不可丢失,需要调用方感知并处理异常。

CallerRunsPolicy(推荐)

// 推荐策略:由调用者线程执行,天然降级
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 4, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

优点

  • 不会丢失任务
  • 调用者线程执行任务时无法提交新任务,形成负反馈(天然限流)
  • 适合批量数据处理场景

风险:调用者线程可能被长时间阻塞。

DiscardPolicy

// 静默丢弃
new ThreadPoolExecutor.DiscardPolicy()

适用场景:非关键任务(如统计日志、埋点上报),丢了不影响核心业务。

使用 DiscardPolicy 时务必记录日志或监控

静默丢弃意味着你永远不会知道任务被丢了,建议配套日志告警。

DiscardOldestPolicy

// 丢弃最旧任务
new ThreadPoolExecutor.DiscardOldestPolicy()

适用场景:实时性要求高,新任务比旧任务更有价值的场景(如实时价格推送)。

DiscardOldestPolicy 与 PriorityBlockingQueue 的冲突

该策略会丢弃队列头部的任务。当使用 PriorityBlockingQueue 时,头部是最优优先级的任务,丢弃它可能导致真正重要的任务被丢掉。

选型建议

场景推荐策略
核心业务,任务不能丢CallerRunsPolicy(或自定义策略 + 告警)
非核心,允许快速失败AbortPolicy
非关键数据(日志/埋点)DiscardPolicy(+ 监控告警)
实时数据流(新>旧)DiscardOldestPolicy

自定义拒绝策略

// 自定义策略:记录日志后写死信队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 4, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    (r, executor1) -> {
        // 记录告警
        log.warn("任务被拒绝,线程池已满,队列已满");
        // 写入死信队列或数据库
        deadLetterQueue.offer(r);
    }
);

相关笔记