文章目录
- 1、报错
- 2、定位
- 3、修复
- 4、线程池使用的一点思考
1、报错
检索项目日志时,发现一个异常堆栈信息,核心报错:
java.util.concurrent.RejectedExecutionException:
Task java.util.concurrent.CompletableFuture$AsyncSupply@480a10c7
rejected from java.util.concurrent.ThreadPoolExecutor@51313448
[Running, pool size = 32, active threads = 32, queued tasks = 200, completed tasks = 2487]
// ...
关键字:RejectedExecutionException
,从这个异常可以看出,这是因为往线程池提交的任务数量超过了最大线程数 + 阻塞队列长度,然后任务再提交过来,线程池无法再接收新的任务,走了默认的拒绝策略 AbortPoligy:直接抛出RejectedExecutionExption异常阻止系统正常运行
2、定位
查看堆栈信息里的信息,定位到异常代码出自:
@Service
public class InspectionService {
private static final Logger log = LoggerFactory.getLogger(InspectionService.class);
private final ExecutorService pool = new ThreadPoolExecutor(
16, // 常驻线程数量
32, // 最大线程数量
10, // 线程存活时间,线程多长时间没被使用就关闭
TimeUnit.SECONDS, // 上一个参数的单位
new LinkedBlockingQueue<>(200), // 阻塞队列
new ThreadFactoryBuilder().setNameFormat("InspectionService-%d").build(), // 线程工厂对象
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// ....
再下面CompleteableFuture再提交任务到线程池:
CompletableFuture.supplyAsync(() -> service.queryGroupInfo(), pool)
//....
提交速度 >>
线程池处理速度
3、修复
调大了阻塞队列的长度:
private final ExecutorService pool = new ThreadPoolExecutor(
16,
32,
10,
TimeUnit.SECONDS,
// groupInfos.size < 500, 这里取length * 1.5
new LinkedBlockingQueue<>(700),
new ThreadFactoryBuilder().setNameFormat("InspectionService-%d").build(),
new ThreadPoolExecutor.AbortPolicy()
);
4、线程池使用的一点思考
- 线程池自己定义,JDK中静态方法创建的阻塞队列长度为Long.MAX_VALUE,风险很高
参数 | 设置要点 | 示例 |
---|---|---|
corePoolSize | CPU密集型:N+1(N=CPU核数)IO密集型:2N | 8核服务器:CPU密集型设9,IO密集型设16 |
maximumPoolSize | 不超过系统最大线程数(cat /proc/sys/kernel/threads-max) | 通常设置 coreSize 的 2-4 倍 |
blockQueue | 有界队列!无界队列=内存炸弹 | new LinkedBlockingQueue<>(200) |
keepAliveTime | 线程空闲回收时间(建议30-60秒) | TimeUnit.SECONDS + 10 |
- 线程池中的线程带名字,不要用默认的pool-xxx,出现问题不好定位是哪儿的任务
- 可以加必要监控,采集日志分析,到达阈值时告警处理
// 定时采集关键指标(每30秒)
// 单例调度线程池
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
if (pool instanceof ThreadPoolExecutor executor) {
int activeCount = executor.getActiveCount();
int queueSize = executor.getQueue().size();
long completedTaskCount = executor.getCompletedTaskCount();
log.info("线程池状态:活跃线程:{} 队列堆积:{} 完成数:{}", activeCount, queueSize, completedTaskCount);
if (queueSize > 500) {
log.warn("线程池队列堆积过多,队列堆积:{} 完成数:{}", queueSize, completedTaskCount);
}
}
}, 0, 30, TimeUnit.SECONDS);
}
- 核心业务与非核心业务使用独立线程池,做到资源隔离