写完一个定时任务,跑着跑着就卡住了;后台接口突然开始大量报“RejectedExecutionException”;服务器内存一路飙到95%,但CPU却不高……这些现象,十有八九跟线程池没管好有关。
别乱 new Thread(),那是给自己挖坑
新手常犯的错:每次处理请求都 new 一个 Thread。看似简单,实则埋雷——线程创建销毁开销大,数量失控后系统直接变卡,还容易 OOM。线程池不是“开了就行”,得像管仓库一样:设上限、看库存、清积压。
核心三件事:大小、队列、拒绝策略
以 Java 的 ThreadPoolExecutor 为例,初始化时必须明确三个关键参数:
new ThreadPoolExecutor(
corePoolSize, // 核心线程数,常驻不灭
maxPoolSize, // 最大线程数,扛峰值用
keepAliveTime, // 空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000), // 任务队列,容量要设!
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略,别用默认的 AbortPolicy
);重点来了:队列不设容量?默认是 Integer.MAX_VALUE,等于把所有任务全塞进去——内存爆掉前,你只会看到 GC 频繁、响应变慢,却找不到哪出问题。
真实场景:为什么定时任务越跑越慢?
某同学写了每分钟执行一次的数据库清理任务,用的是 Executors.newFixedThreadPool(5)。结果两周后系统变卡。查日志发现:某次数据库连不上,任务一直阻塞在队列里,后续几百个任务全堆着,线程池“假活”——看着在跑,其实都在等一个失败的连接超时(默认30秒)。解决办法很简单:
① 把 FixedThreadPool 换成带界队列的自定义池;
② 清理任务加超时控制(比如用 CompletableFuture.orTimeout);
③ 监控队列长度,超过 200 就告警。
怎么知道线程池是不是快崩了?
别等报警才看。加两行日志,放在关键任务前后:
log.info("[线程池监控] 队列大小:{},活跃线程:{},已执行:{}",
executor.getQueue().size(),
executor.getActiveCount(),
executor.getCompletedTaskCount());再配合 Prometheus + Grafana,画个“队列长度随时间变化”图,高峰时段是否持续堆积,一眼就清楚。
拒绝策略不是摆设,选错等于放弃治疗
默认的 AbortPolicy 遇到满载直接抛异常,前端看到 500;CallerRunsPolicy 让调用线程自己执行任务,能缓解压力但可能拖慢主线程;DiscardOldestPolicy 丢掉最老的排队任务,适合实时性要求高的场景(如消息推送);DiscardPolicy 最狠——静默丢弃,不留痕迹,排查起来最头疼。选哪个,得看你业务能不能容忍丢失、延迟或降级。
最后提醒一句
别把所有业务塞进同一个线程池。IO 密集型(比如发 HTTP 请求)和 CPU 密集型(比如图像压缩)混在一起,不是互相拖垮就是资源浪费。分池管理,各司其职,才叫真·管理。