线程池实战经验
本章节整理一版线程池实战经验,这部分非常贴近面试官爱考、现场工作爱用的内容,供大家参考!
1. 如何自定义 ThreadFactory?要注意什么?
标准回答:
- 自定义
ThreadFactory,通常是为了: - 给线程命名,便于日志排查、监控。
- 设置是否是守护线程(Daemon)。
- 设置线程优先级,必要时使用。
示例代码:
ThreadFactory customThreadFactory = runnable -> {
Thread thread = new Thread(runnable);
thread.setName("my-pool-thread-" + thread.getId());
thread.setDaemon(false); // 生产一般设置为非守护线程
return thread;
};
注意事项:
- 保证线程创建逻辑简单、稳定,不能有副作用。
- 命名统一规范,便于排查问题。
- 避免线程创建过多导致资源耗尽。
2. 如何避免线程池中的内存泄漏问题?
标准回答:
- 根本原因:提交到线程池的任务对象持有大对象引用,导致任务即使完成也不能被 GC。
- 常见场景:匿名内部类、Lambda 捕获外部大对象。
解决方法:
- 不要在任务中不小心引用外部大对象。
- 用静态内部类代替非静态内部类,减少对外部引用的隐式持有。
- 定时清理过期任务,或者使用合理的生命周期管理。
举个坑例子:
BigObject bigObject = new BigObject();
executor.submit(() -> {
// 错误!这里 Lambda 捕获了 bigObject
bigObject.doSomething();
});
应该改成:
executor.submit(new BigObjectTask(bigObject));
3. 不同业务场景,线程池参数应该怎么配?
标准回答:
| 业务类型 | corePoolSize / maximumPoolSize | 队列类型 | 特点 |
|---|---|---|---|
| CPU密集型(计算、加密) | 核心线程数 = CPU核数(n)或 n+1 | SynchronousQueue 或 ArrayBlockingQueue | 保证 CPU 不切换上下文 |
| IO密集型(数据库、文件) | 核心线程数 = 2n ~ 4n | LinkedBlockingQueue | 避免 IO 阻塞导致 CPU 空闲 |
| 高并发低延迟系统 | 核心线程数 = 2n,最大线程数很大 | SynchronousQueue | 响应快,牺牲一些资源消耗 |
| 普通后台处理 | 核心小(如2-4),最大适中(如8-16) | LinkedBlockingQueue | 保持系统稳定性优先 |
总结一句话:
“CPU 密集限数量,IO 密集放宽,延迟敏感快扩容。”
4. 如何合理设置 keepAliveTime?
标准回答:
- 如果线程扩展主要用于短时流量高峰,可以设置较小的 keepAliveTime,比如 30 秒、60 秒。
- 如果希望线程存活时间长一点(减少频繁销毁带来的开销),可以设置大一点,比如 5 分钟。
- 对核心线程调用
allowCoreThreadTimeOut(true)时,要特别小心,防止线程池收光,导致线程池频繁创建新线程 ➔ 反而更慢。
经验值:
一般 keepAliveTime = 1分钟以内就够用,特例除外。
5. 线程池调优时有哪些监控指标必须盯住?
标准回答:
关键指标:
| 指标 | 说明 | 重点 |
|---|---|---|
| activeCount | 正在运行的线程数 | 过高说明线程忙不过来 |
| queueSize | 队列中等待的任务数 | 持续上升说明处理不过来 |
| completedTaskCount | 已完成任务数 | 反映处理能力 |
| largestPoolSize | 曾经达到的最大线程数 | 评估配置是否合理 |
| rejectionCount | 被拒绝的任务数量 | 监控业务异常风险 |
结论:
监控 ➔ 调整参数 ➔ 防止排队堆积 ➔ 保证系统稳定。
6. 大量短任务适合怎样的线程池设计?
标准回答:
- 如果任务很短(比如几十毫秒内完成),应该:
- 设置较大的核心线程数,避免频繁提交、排队。
- 使用较小或者同步型的队列(如
SynchronousQueue)。 - 保证 CPU 尽量保持忙碌,而不是频繁切换上下文。
选型推荐:
ThreadPoolExecutor + SynchronousQueue,配合合理的最大线程数,最适合处理高频短任务。
7. 如何防止线程池中的任务“卡死”?
标准回答:
- 给任务设置超时,比如通过
Future.get(timeout, TimeUnit)来限时拿结果。 - 在线程池外控制每个任务的最大执行时间。
- 提交可取消的任务(比如用
Callable而不是Runnable)。
例子:
Future<?> future = executor.submit(task);
try {
future.get(5, TimeUnit.SECONDS); // 最多等5秒
} catch (TimeoutException e) {
future.cancel(true); // 超时后取消任务
}
结论:
不信任任何任务能快速完成,要自己兜底。
更多详细内容请关注其他相关文章!