电脑课堂
柔彩主题三 · 更轻盈的阅读体验

线程池怎么管理?Java开发中常见的卡顿、OOM和拒绝问题这样解

发布时间:2026-01-22 15:50:59 阅读:72 次

写完一个定时任务,跑着跑着就卡住了;后台接口突然开始大量报“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 密集型(比如图像压缩)混在一起,不是互相拖垮就是资源浪费。分池管理,各司其职,才叫真·管理。