Java线程池
线程池解决的核心问题是"线程创建销毁成本高"——每次 new Thread 都要走操作系统调用,高并发下频繁创建线程既慢又占内存,线程池复用线程把成本摊薄。Java 的线程池实现是 ThreadPoolExecutor,掌握它的七个参数和四种拒绝策略,就能理解 Executors 工厂方法各自的问题,从而正确配置线程池。
Executor 框架
Executor 框架把"任务的提交"与"任务的执行"解耦:提交方只调用 execute()/submit(),不关心任务由哪个线程、何时执行。
| 接口 | 职责 |
|---|---|
Executor | 最顶层,只有 execute(Runnable) |
ExecutorService | 扩展:submit() 返回 Future、shutdown() 关闭 |
ThreadPoolExecutor | 核心实现类,可配置参数 |
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.execute(() -> System.out.println("任务执行"));
pool.shutdown(); // 提交完必须关闭,否则线程不回收ThreadPoolExecutor 七大参数
new ThreadPoolExecutor(
corePoolSize, // 1. 核心线程数:常驻线程,不回收
maximumPoolSize, // 2. 最大线程数:核心 + 临时线程上限
keepAliveTime, // 3. 临时线程空闲存活时间
TimeUnit.SECONDS, // 4. 时间单位
workQueue, // 5. 任务队列:核心线程忙时任务先排队
threadFactory, // 6. 线程工厂:定义线程名、是否守护
handler // 7. 拒绝策略:队列满 + 线程满时的处理
);前两个参数控制线程数量,队列参数决定"线程先扩还是队列先排"——默认是队列先排,队列满了才创建临时线程。
工作流程
记住这个顺序
核心线程 → 任务队列 → 临时线程 → 拒绝策略。线程池"先排队后扩线程"的设计是为了避免线程频繁创建销毁。
四种拒绝策略
| 策略 | 行为 | 适用场景 |
|---|---|---|
AbortPolicy(默认) | 抛 RejectedExecutionException | 不能接受任务丢失 |
CallerRunsPolicy | 由提交任务的线程自己执行 | 降级但不丢任务 |
DiscardPolicy | 静默丢弃 | 允许丢弃(日志场景) |
DiscardOldestPolicy | 丢弃队列最老的,重试提交 | 追求最新数据 |
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);慎用 Executors 工厂方法
Executors.newFixedThreadPool() 使用无界 LinkedBlockingQueue,任务堆积时队列无限增长,可能 OOM;newCachedThreadPool() 最大线程数是 Integer.MAX_VALUE,突发任务会创建海量线程拖垮机器。生产环境建议手动 new ThreadPoolExecutor,明确队列边界和拒绝策略。
参数如何配置
没有万能公式,按任务类型给经验值:
| 任务类型 | 核心线程数参考 | 说明 |
|---|---|---|
| CPU 密集型 | CPU 核数 + 1 | 线程超过核数只会增加切换开销 |
| I/O 密集型 | CPU 核数 × 2 ~ 2 倍多 | 线程等待 I/O 时可让出 CPU |
| 混合型 | 拆分为 CPU 池 + I/O 池 | 避免相互拖累 |
// 获取 CPU 核数
int cores = Runtime.getRuntime().availableProcessors();队列容量怎么定
队列太小会过早触发拒绝,队列太大任务延迟高。经验:队列容量 = 核心线程数 × 单任务平均执行时间 ÷ 期望响应时间,先估算再压测调整。
关闭线程池
| 方法 | 行为 |
|---|---|
shutdown() | 不再接受新任务,执行完队列中已有任务后关闭 |
shutdownNow() | 立即中断所有任务并清空队列,返回未执行任务列表 |
awaitTermination(timeout) | 阻塞等待关闭完成,返回是否按时关闭 |
pool.shutdown();
if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
pool.shutdownNow(); // 超时强制关闭
}线程池与 Java并发编程 的 ThreadLocal 有经典冲突:线程池复用线程导致 ThreadLocal 值跨任务残留,用完必须 remove()。阻塞队列(ArrayBlockingQueue 等)是线程池任务队列的底层实现,见 Java并发编程 的阻塞队列章节;锁与同步机制见 Java锁机制。