Java并发编程之线程池详解
关键词:Java 并发、线程池、ThreadPoolExecutor、线程管理、线程复用、性能优化
✅ 摘要
在高并发系统中,频繁地创建和销毁线程会带来显著的性能开销。为了解决这个问题,Java 提供了线程池机制来统一管理和复用线程资源。线程池是 Java 并发编程中的核心组件之一,广泛应用于任务调度、异步处理、定时任务等场景。
本文将全面讲解 Java 中线程池的核心概念、工作原理、常见参数配置、拒绝策略、自定义线程池、最佳实践等内容,并提供大量 可运行的示例代码,帮助你深入理解线程池的使用方式与底层机制。
📌 一、为什么需要线程池?
1.1 线程的生命周期成本
每次新建一个线程都要进行:
- 内存分配
- 栈空间初始化
- 线程注册操作系统资源
- 调度器调度线程执行
这些操作在高并发下会产生明显的性能瓶颈。
1.2 线程池的作用
- 线程复用:避免频繁创建和销毁线程
- 控制最大并发数:防止系统因线程过多而崩溃
- 任务队列管理:实现任务排队、延迟执行等功能
- 提高响应速度:线程提前准备好,直接执行任务
📌 二、Java 中的线程池体系结构
2.1 主要类图关系
Executor↑
ExecutorService↑
AbstractExecutorService↑
ThreadPoolExecutor
Executor
:最顶层接口,只有一个execute(Runnable)
方法ExecutorService
:扩展了提交任务的方法(submit)、关闭方法等ThreadPoolExecutor
:线程池的核心实现类
📌 三、线程池的核心参数详解
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
参数 | 说明 |
---|---|
corePoolSize | 核心线程数,即使空闲也不会被回收 |
maximumPoolSize | 最大线程数,当任务队列满时可以扩容至该值 |
keepAliveTime | 非核心线程的最大存活时间 |
unit | 存活时间单位 |
workQueue | 任务等待队列 |
threadFactory | 创建新线程的工厂 |
handler | 拒绝策略 |
📌 四、线程池的工作流程详解
线程池的任务执行流程如下:
- 如果当前线程数 <
corePoolSize
,则新建线程执行任务; - 否则,将任务加入阻塞队列;
- 如果队列已满且当前线程数 <
maximumPoolSize
,则新建非核心线程执行任务; - 如果队列已满且线程数 >=
maximumPoolSize
,则触发拒绝策略。
📌 注意:如果使用无界队列(如 LinkedBlockingQueue
),maximumPoolSize
将失效。
📌 五、常见的线程池类型(Executors 工具类)
Java 提供了几个常用的线程池工厂方法:
5.1 固定大小线程池(FixedThreadPool)
ExecutorService executor = Executors.newFixedThreadPool(5);
- 核心线程数 = 最大线程数
- 使用无界队列(
LinkedBlockingQueue
) - 适用于负载较重、任务数量稳定的场景
5.2 缓存线程池(CachedThreadPool)
ExecutorService executor = Executors.newCachedThreadPool();
- 核心线程数为 0,最大线程数为 Integer.MAX_VALUE
- 非核心线程超时时间为 60 秒
- 适用于执行大量短期异步任务的场景
5.3 单线程线程池(SingleThreadExecutor)
ExecutorService executor = Executors.newSingleThreadExecutor();
- 只有一个线程,保证任务顺序执行
- 使用无界队列
- 适用于需要顺序执行任务的场景
5.4 定时任务线程池(ScheduledThreadPool)
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
executor.scheduleAtFixedRate(() -> {System.out.println("每两秒执行一次");
}, 0, 2, TimeUnit.SECONDS);
- 支持定时任务和周期性任务执行
- 常用于心跳检测、日志采集、定时清理等场景
📌 六、线程池的拒绝策略
当任务无法提交时(队列满 + 线程数已达上限),线程池会调用拒绝策略处理器。
Java 提供了以下几种内置拒绝策略:
策略 | 行为 |
---|---|
AbortPolicy (默认) | 抛出 RejectedExecutionException 异常 |
CallerRunsPolicy | 由调用线程(提交任务的线程)自己执行该任务 |
DiscardOldestPolicy | 丢弃队列中最老的一个任务,尝试再次提交 |
DiscardPolicy | 默默丢弃任务,不抛异常也不执行 |
自定义拒绝策略示例:
RejectedExecutionHandler handler = (r, executor) -> {System.out.println("任务被拒绝:" + r.toString());
};ExecutorService executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.CallerRunsPolicy()
);
📌 七、线程池的最佳实践
7.1 不要使用 Executors 工厂方法创建线程池(推荐自定义)
因为:
newFixedThreadPool
和newSingleThreadExecutor
使用的是 无界队列,可能导致内存溢出newCachedThreadPool
的最大线程数是无限的,可能耗尽系统资源
✅ 推荐做法:使用 ThreadPoolExecutor
显式指定参数
ExecutorService executor = new ThreadPoolExecutor(5, 10,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.AbortPolicy()
);
7.2 合理设置参数
场景 | 推荐参数 |
---|---|
CPU 密集型任务 | corePoolSize = CPU核心数 |
IO 密集型任务 | corePoolSize = CPU核心数 * 2 |
高吞吐任务 | 设置较大的队列容量和合理的拒绝策略 |
7.3 关闭线程池
executor.shutdown(); // 温和关闭,不再接受新任务,等待已有任务完成
executor.shutdownNow(); // 强制关闭,尝试中断正在执行的任务
📌 八、线程池的监控与调试
可以通过继承 ThreadPoolExecutor
或使用 ThreadPoolTaskExecutor
(Spring 提供)来监控线程池状态。
@Override
protected void beforeExecute(Thread t, Runnable r) {System.out.println("任务开始执行:" + r);
}@Override
protected void afterExecute(Runnable r, Throwable t) {System.out.println("任务结束执行:" + r);
}
📌 九、完整示例代码
import java.util.concurrent.*;public class ThreadPoolDemo {public static void main(String[] args) {ExecutorService executor = new ThreadPoolExecutor(2, 4,60L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 0; i < 10; i++) {final int taskNo = i;executor.execute(() -> {System.out.println("执行任务 " + taskNo + ",线程:" + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}executor.shutdown();}
}
✅ 总结
特性 | 说明 |
---|---|
线程池作用 | 提高线程复用率、降低系统开销 |
核心参数 | corePoolSize、maximumPoolSize、workQueue、handler |
工作流程 | 核心线程 → 队列 → 扩容 → 拒绝 |
拒绝策略 | Abort、CallerRuns、DiscardOldest、Discard |
最佳实践 | 显式创建线程池、合理设置参数、优雅关闭 |
应用场景 | 并发请求处理、定时任务、批量数据处理等 |
📚 参考资料
- Java 官方文档 - ExecutorService