线程池机制与企业级实践
线程池的核心思想
线程池通过预先初始化一组工作线程,实现任务的复用执行,避免频繁创建和销毁线程带来的系统开销。其核心目标是提升并发处理效率并降低资源消耗。
引入线程池的优势
- 减少开销:避免每次任务都进行线程创建/销毁的系统调用。
- 快速响应:已有线程可立即处理新任务,缩短延迟。
- 可控性高:可精确设定最大线程数、任务队列容量及拒绝策略。
Java中的线程池实现
主要接口为 ExecutorService,但推荐直接使用 ThreadPoolExecutor 构造器而非 Executors 工厂方法,以防止潜在内存溢出风险。
关键构造参数解析
new ThreadPoolExecutor(
corePoolSize, // 核心线程数量
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100), // 有界任务队列
new ThreadFactory() { ... }, // 自定义线程创建逻辑
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
任务执行流程(面试高频)
- 若当前线程数小于核心数 → 新建线程执行任务
- 否则 → 尝试将任务加入阻塞队列
- 若队列已满且线程数未达上限 → 创建额外线程
- 若队列满且已达最大线程数 → 触发拒绝策略
口诀记忆:先核心,再排队,超限扩,最后拒。
四种拒绝策略对比
| 策略 | 行为说明 |
|---|---|
AbortPolicy |
抛出 RejectedExecutionException |
CallerRunsPolicy |
由调用线程直接运行任务 |
DiscardPolicy |
静默丢弃任务 |
DiscardOldestPolicy |
移除队列中最久的任务,尝试重新入队 |
典型线程池类型分析
- 固定大小池:线程数恒定,但默认使用无界队列,易引发内存问题。
- 缓存线程池:动态创建线程,无上限,存在过度创建风险。
- 单线程池:保证任务顺序执行,适用于需串行控制的场景。
- 定时任务池:支持延时或周期性调度,用于定时作业。
企业级线程池配置范例
ExecutorService taskExecutor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 空闲存活时间(秒)
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列,防溢出
r -> new Thread(r, "worker-thread-" + System.currentTimeMillis()),
new ThreadPoolExecutor.CallerRunsPolicy()
);
线程池状态机
线程池维护五种状态,用于管理生命周期:
| 状态 | 含义 |
|---|---|
RUNNING |
接受新任务并执行 |
SHUTDOWN |
不再接收新任务,继续处理已有任务 |
STOP |
中断所有任务,不接受新任务 |
TIDYING |
所有任务结束,准备清理 |
TERMINATED |
完全终止,资源释放完成 |
关闭方式对比
shutdown():平滑关闭,等待现有任务完成。shutdownNow():强制中断,返回待执行任务列表。
最佳实践建议
- 禁止使用
Executors工厂类,避免隐式风险。 - 合理设置线程数:
- CPU密集型:线程数 ≈ 核数 + 1
- I/O密集型:线程数 ≈ 核数 × 2
- 始终使用有界队列,如
ArrayBlockingQueue。 - 自定义线程名称,便于日志追踪与故障排查。
总结(面试要点)
线程池本质是:资源复用 + 并发控制 + 性能优化。
核心四要素:核心线程数、最大线程数、任务队列、拒绝策略。
执行路径:核心线程 → 队列缓冲 → 扩展线程 → 拒绝处理。