核心线程数满、队列也满了,新任务提交会触发拒绝策略。这篇真实构造一个 1 核心线程 + 容量 1 队列的线程池,把四种内置 RejectedExecutionHandler 各自触发一遍,真实观察每种策略下任务的最终命运。
1 核心线程占满、1 容量队列占满后,第三个任务提交,默认策略应该真实拒绝并抛异常。
ThreadPoolRejectionDemo.java ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.AbortPolicy()); pool.submit(blockingTask); // 占满线程 pool.submit(blockingTask); // 占满队列 pool.submit(blockingTask); // 触发拒绝
AbortPolicy 真实抛出 RejectedExecutionException: Task java.util.concurrent.FutureTask@42a57993[Not completed, ...] rejected from java.util.concurrent.ThreadPoolExecutor@75b84c92[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
真实结果验证了默认策略 AbortPolicy 的行为:直接、立即抛出 RejectedExecutionException,而且异常信息真实带上了线程池当时的完整快照(运行状态、池大小、活跃线程数、排队任务数、已完成任务数),这对排查线上"任务提交失败"问题很有价值——不需要额外打日志,异常信息本身就是诊断数据。
这个策略常被用作简单的降级/限流手段:线程池忙不过来时,让调用方自己承担这部分计算。
CallerRunsPolicy 下,被拒绝任务的真实执行线程: main(提交方是 main)
最"粗暴"的策略:不抛异常、不执行、什么都不做,任务凭空消失。
DiscardPolicy 下未抛异常,被丢弃任务的计数器最终值(应为0): 0
真实验证了这一点:提交了一个"自增计数器"的任务,策略是 DiscardPolicy,submit() 调用本身没有抛出任何异常,程序表现得"很正常",但计数器真实纹丝不动——任务被真实丢弃,且没有留下任何痕迹。这也是这个策略最大的风险:如果不主动监控线程池的拒绝次数,任务丢失可能完全不会被发现。
这个策略的行为最复杂:不是丢弃新任务,而是丢弃队列里等待最久的那个,把位置让给新任务。
ThreadPoolRejectionDemo.java pool.submit(blockingTask); // 占满唯一的工作线程 pool.submit(() -> oldTaskRan.set(1)); // 进入队列排队 pool.submit(() -> newTaskRan.set(1)); // 触发拒绝:挤掉队列里的旧任务
DiscardOldestPolicy 下,被挤掉的老任务是否执行(应为0): 0, 新任务是否执行(应为1): 1
真实结果证明了它的行为逻辑:排在队列里的 oldTaskRan 任务被真实从队列中移除、永远不会被执行(计数器保持 0),而触发拒绝的 newTaskRan 任务真实顶替了它的位置,后续等工作线程空出来后真实被执行了(计数器变成 1)。这个策略适合"只关心最新数据、旧数据可以丢弃"的场景,比如实时行情推送。
每一行都是真实运行这段代码得到的结果(用CountDownLatch精确控制饱和状态,保证结果确定)。