2021-04-26 23:57:34
Java线程池拒绝执行异常(RejectedExecutionException)的排查和解决需从任务提交速率、线程池参数配置、拒绝策略选择三方面入手,结合案例分析调整方案。
一、异常根源分析案例数据:线程池配置为160个核心线程(8核16线程服务器的20倍),等待队列长度10000,但任务积压达10000且持续新增。
问题表现:所有线程繁忙,队列满载,AbortPolicy策略直接抛出异常。
监控线程池状态:
通过ThreadPoolExecutor的实例方法获取关键指标:
getActiveCount():活动线程数。
getQueue().size():等待队列任务数。
getCompletedTaskCount():已完成任务数。
案例指标:活动线程160(满载),队列10000(满载),已完成588179(长期积压)。
分析任务生成速率:
统计单位时间内提交的任务数量,对比线程池处理能力(核心线程数 + 队列吞吐量)。
案例问题:任务生成速率持续高于线程池处理速率(160线程 + 队列10000仍无法消化)。
检查线程池参数配置:
核心参数:
corePoolSize:核心线程数(案例中为160,远超CPU核心数)。
maximumPoolSize:最大线程数(案例中与核心线程数相同,无弹性)。
workQueue:等待队列类型及容量(案例中为LinkedBlockingQueue(10000))。漏闹李
handler:拒绝策略(案例中为AbortPolicy)。
配置问题:核心线程数过高,队列过长,拒绝策略过于严格。
建议值:接近CPU核心数(如8核服务器设为8~16)。
案例调整:从160降至16(与服务器线程数匹配)。
建议值:略高于核心线程数(如核心线程的1.5~2倍)。
案例调整:从160降至32(提供弹性扩容空间)。
建议类型:根据任务特性选择:
有界队列(如ArrayBlockingQueue):防止内存溢出。
同步移交队列(如SynchronousQueue):适用于高吞吐、低延迟场景。
案例调整:从LinkedBlockingQueue(10000)改为ArrayBlockingQueue(1000)(限制队列长度)。
CallerRunsPolicy:让提交任务的线程自行执行,降低提交速率。
适用场景:需要保证任务不丢失且可接受延迟。
DiscardPolicy:直接丢弃新任务。
适用场景:允许任返迟务丢失(如日志记录)。
DiscardOldestPolicy:丢弃队列中最旧的任务。
适用场景:优先处理新任务(如实时数据)。
使用Semaphore或RateLimiter控弯裤制任务提交速率。
示例代码:RateLimiter limiter = RateLimiter.create(100); // 每秒100个任务for (Task task : tasks) { limiter.acquire(); threadPool.execute(task);}
对非实时任务,改用消息队列(如Kafka、RabbitMQ)缓冲任务。
通过JMX或Micrometer暴露线程池指标(活动线程数、队列长度、拒绝次数)。
示例代码:ThreadPoolExecutor executor = new ThreadPoolExecutor(...);MetricRegistry registry = new MetricRegistry();registry.register("thread.pool.active", new Gauge<Integer>() { @Override public Integer getValue() { return executor.getActiveCount(); }});
根据负载动态修改线程池参数(需谨慎,可能引发竞争条件)。
线程池参数需与CPU资源、任务特性匹配。
拒绝策略需根据业务容错性选择。
任务提交速率需通过限流或异步化控制。