数智应用帮
柔彩主题三 · 更轻盈的阅读体验

线程池怎么管理?手把手教你日常开发中稳住并发

发布时间:2026-01-22 15:50:33 阅读:188 次

你写了个定时任务,每分钟拉一次天气数据;又加了个后台服务,批量处理用户上传的Excel;结果服务器CPU突然飙到95%,日志里全是“拒绝执行”——十有八九,是线程没管好。

别一上来就 new ThreadPoolExecutor

很多新手看到“线程池”三个字,第一反应是翻文档、抄参数、配个 coreSize=4、maxSize=8、队列用 LinkedBlockingQueue……跑两天就出问题。不是参数不对,是没想清楚:这个池子到底要干啥活?

比如你有个接口,要调第三方短信网关,单次耗时 300ms 左右,QPS 稳定在 50。那线程数不是拍脑袋定的:按公式 线程数 ≈ CPU核数 × (1 + 平均等待时间 / 平均工作时间) 算,假设是 4 核机器,实际需要约 4 × (1 + 300/20) ≈ 64 个线程——但真开64个?小心把对方网关打挂。这时候更合理的做法,是限制并发数(比如用 Semaphore),再配一个较小的线程池(如 core=4, max=8)+ 有界队列(ArrayBlockingQueue,容量设为50),超了就快速失败,别堆着等。

关键不是“建”,是“盯”和“调”

线程池建完不等于万事大吉。Java 里可以通过 ThreadPoolExecutor 的几个方法实时看状态:

System.out.println("活跃线程数:" + pool.getActiveCount());
System.out.println("已完成任务数:" + pool.getCompletedTaskCount());
System.out.println("队列剩余容量:" + pool.getQueue().remainingCapacity());

建议在监控页面或健康检查接口里暴露这些值。某次上线后发现队列一直积压,查日志才发现下游 Redis 响应变慢,导致线程全卡在 I/O 上——立刻切到本地缓存降级,比改线程数管用十倍。

别共用一个“万能池”

见过最猛的操作:整个项目只定义一个 Executors.newFixedThreadPool(20),所有异步操作全往里塞——定时任务、发邮件、生成PDF、甚至用户点击“收藏”都走它。结果 PDF 生成卡住 2 秒,收藏接口跟着超时。

正确姿势是分场景建池:
• I/O 密集型(调HTTP、DB、Redis)→ 线程数稍多,用 CachedThreadPool 或自定义带超时的池;
• CPU 密集型(图像压缩、加密计算)→ 线程数 ≈ CPU 核数,避免上下文切换开销;
• 定时任务 → 单独用 ScheduledThreadPoolExecutor,别混进普通池。

记得关,不然应用停不掉

Spring Boot 项目里,如果手动 new 了线程池,又没在 @PreDestroySmartLifecycle 里调 shutdown(),应用重启时线程还在跑,端口占着、连接连着、内存不释放——运维同学半夜接到告警,第一句就是:“你那个服务没关干净。”

简单收尾写法:

public void destroy() {
if (taskExecutor != null && !taskExecutor.isShutdown()) {
taskExecutor.shutdown();
try {
if (!taskExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
taskExecutor.shutdownNow();
}
} catch (InterruptedException e) {
taskExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

线程池不是银弹,也不是越“大”越好。它像家里的电闸——配对了,电器各用各的不跳闸;配错了,吹风机一开,冰箱也断电。管好它,先从看清自己在跑什么任务开始。