最易踩的坑是无界队列导致OOM,应显式使用有界队列(如ArrayBlockingQueue(256)),协同设置线程数与队列容量,配CallerRunsPolicy拒绝策略,并添加命名线程工厂和监控。

直接用 Executors.newFixedThreadPool() 或其他工厂方法,最容易踩的坑就是无界队列——它看起来“能装下所有任务”,实则把内存溢出(OOM)悄悄埋进生产环境。关键不是换不换线程池类型,而是让队列容量“看得见、控得住、有退路”。
显式指定有界队列容量,别依赖默认值
LinkedBlockingQueue 默认构造是 new LinkedBlockingQueue(),内部容量为 Integer.MAX_VALUE,等于开了个无限缓存口子。哪怕只是测试,也必须手动设上限:
- 正确写法:
new LinkedBlockingQueue(256)或更推荐new ArrayBlockingQueue(256) - 容量要结合单任务内存开销估算:比如每个任务携带 DTO + 请求体约 8KB,256 个 ≈ 2MB,可控;设成 10000 就接近 80MB,风险明显上升
- ArrayBlockingQueue 更稳妥:底层是固定数组,内存占用确定、不可扩容,从编码层面杜绝“悄悄膨胀”
线程数与队列容量要协同设计
光设队列大小不够,得和线程调度能力匹配,否则要么压垮线程,要么堆满队列:
- 核心线程数建议按业务类型设置:CPU 密集型 ≈ CPU 核数,IO 密集型可设为核数 × (1 + 阻塞系数),比如 4 核 × 3 = 12
- 若核心/最大线程数都设为 8,队列设 256,则系统最多缓冲 256 个待执行任务;超出立即触发拒绝策略,不会静默堆积
- 避免“大队列 + 小线程数”组合:比如 8 个线程配 10000 容量队列,等于把 OOM 延后,而非规避
配好拒绝策略,让背压真正起作用
队列满了怎么办?不能让它卡死或丢任务。拒绝策略是反压的第一道防线:
- 首选 CallerRunsPolicy:队列满时由提交任务的线程自己执行,天然降低上游提交速率,形成负反馈
- 慎用 AbortPolicy(直接抛异常):除非业务能容忍任务丢失,否则可能引发上游逻辑中断
- DiscardPolicy 和 DiscardOldestPolicy 不适合关键路径:容易丢数据,且无日志记录,问题难追溯
加上命名线程工厂和基础监控
没有可观测性,就等于在黑盒里调参数:
- 用
ThreadFactoryBuilder或自定义工厂,生成带业务前缀的线程名,如"order-process-%d",方便日志排查和监控定位 - 手动构建 ThreadPoolExecutor 后,可实时获取关键指标:
getQueue().size()查积压量,getActiveCount()看活跃线程数 - 阈值建议:队列使用率超 80%、活跃线程数持续等于最大线程数,就该告警并检查上游流量或下游处理瓶颈
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











