线程池常见错误使用场景
本章节针对线程池常见错误使用场景以及解法做了简单的总结,面试时如果讲到这里,绝对是高级选手水平!
❌ 1. 使用 Executors 工厂方法创建线程池(极容易踩坑)
比如这些:
Executors.newFixedThreadPool()
Executors.newSingleThreadExecutor()
Executors.newCachedThreadPool()
Executors.newScheduledThreadPool()
问题:
- 这些方法创建的线程池默认参数不合理。
- 比如:
newFixedThreadPool()和newSingleThreadExecutor()→ 阻塞队列是无限长 LinkedBlockingQueue ➔ 任务堆积 ➔ 内存爆炸newCachedThreadPool()→ 线程数几乎无限扩展 ➔ CPU、内存耗尽 ➔ 系统崩溃
✅ 正确做法:
推荐自己手动 new ThreadPoolExecutor(),合理设置参数。
比如👇
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity), // 有界队列,防止堆积
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
❌ 2. 核心线程数、最大线程数、队列容量参数配置随便写
问题:
- 配得太小 ➔ 线程池忙不过来,任务大量排队,系统响应慢。
- 配得太大 ➔ 占满CPU、内存,导致系统其他线程饿死,系统挂掉。
✅ 正确做法:
- 根据实际业务类型调优。
- IO密集型任务(比如网络通信、数据库查询) ➔ 核心线程数可以设置为 CPU 核心数 × 2
- CPU密集型任务(比如大量计算) ➔ 核心线程数可以设置为 CPU 核心数 + 1
- 队列设置合理大小,不要太大也不要无限大。
- 最大线程数 = 核心线程数 + 扩展保护线程数。
❌ 3. 不设置合理的拒绝策略
问题:
- 默认拒绝策略是
AbortPolicy()➔ 直接抛异常,很多人忽略了导致应用崩溃。 - 其他拒绝策略(比如 CallerRunsPolicy)也有副作用,比如可能导致主线程也去执行任务,降低吞吐量。
✅ 正确做法:
- 根据场景选择合理的拒绝策略:
- 高实时性场景(秒杀系统) ➔
DiscardOldestPolicy(),丢弃旧任务 - 非核心业务 ➔
DiscardPolicy(),直接丢弃 - 核心业务 ➔
CallerRunsPolicy(),让调用方自己执行(要小心主线程被卡住) - 加上报警监控,线程池触发拒绝时一定要记录日志、报警。
❌ 4. 线程池数量太少,导致大量排队超时
问题:
- 核心线程数太低,任务执行慢,用户请求超时,系统不可用。
✅ 正确做法:
- 根据实际的任务量和性能测试数据,动态调节线程池大小,保证任务在可接受的时间内完成。
❌ 5. 多个业务共享一个线程池(线程池资源争抢)
问题:
- 不同业务混在一个线程池里,容易互相影响。
- 比如慢查询业务把整个线程池卡住,导致高优先级业务也执行不了。
✅ 正确做法:
- 业务隔离:重要业务和次要业务使用不同的线程池,避免互相影响。
总结一版万能收尾(可直接在面试结尾用)
“线程池虽然能大幅提升系统性能和资源利用率,但如果参数配置不合理、拒绝策略处理不当或者业务没有隔离,就很容易导致系统雪崩。所以实际开发中要根据业务特点精细化调优线程池配置,并配合监控与报警机制,保障系统稳定运行。”
更多详细内容请关注其他相关文章!