Java线程池详解
Java 线程池是管理线程生命周期、控制并发度的核心组件,基于 “池化思想” 减少线程创建 / 销毁的开销,优化系统资源利用率,同时提供任务队列、拒绝策略等机制,确保并发编程的稳定性和可维护性。
1.为什么需要线程池
直接创建线程存在以下问题:
- 资源开销大:线程创建 / 销毁需要操作系统分配内核资源(如 PCB),频繁操作会消耗大量 CPU 和内存;
- 并发失控:无限制创建线程会导致 CPU 上下文切换频繁,甚至 OOM(线程栈占用内存,默认 1MB);
- 管理复杂:手动维护线程的创建、执行、销毁,难以处理任务队列、异常捕获等场景。
线程池的核心优势:
- 复用线程,减少创建 / 销毁开销;
- 控制最大并发数,避免资源耗尽;
- 提供任务缓冲队列,削峰填谷;
- 支持任务拒绝、线程监控、定时执行等高级功能。
2.核心组件 ThreedPoolExecutor
Java 线程池的底层实现是java.util.concurrent.ThreadPoolExecutor,所有线程池(如Executors创建的)本质都是它的封装。
1.核心构造方法
public ThreadPoolExecutor( int corePoolSize, // 核心线程数(常驻线程,即使空闲也不销毁) int maximumPoolSize, // 最大线程数(核心线程+临时线程的总上限) long keepAliveTime, // 临时线程空闲存活时间 TimeUnit unit, // keepAliveTime的时间单位(如TimeUnit.SECONDS) BlockingQueue<Runnable> workQueue, // 任务阻塞队列(核心线程满时存任务) ThreadFactory threadFactory, // 线程工厂(用于创建线程,可自定义命名、优先级) RejectedExecutionHandler handler // 拒绝策略(任务队列满+线程达最大数时的处理方式) ) {}2.核心参数详解
1)核心线程数(corePoolSize)
- 线程池的 “常驻线程”,即使空闲也不会销毁(除非设置
allowCoreThreadTimeOut(true)); - 任务提交时,若核心线程未满,直接创建核心线程执行任务。
(2)最大线程数(maximumPoolSize)
- 线程池能容纳的最大线程数(核心线程 + 临时线程);
- 核心线程满且队列满时,会创建临时线程执行任务;
- 临时线程空闲超过
keepAliveTime会被销毁,恢复到核心线程数。
(3)任务队列(workQueue)
- 核心线程满时,新任务会进入队列等待;
- 必须使用阻塞队列(BlockingQueue),支持线程安全的入队 / 出队操作,常用实现:
LinkedBlockingQueue:无界队列(默认容量Integer.MAX_VALUE),风险:任务堆积导致 OOM;ArrayBlockingQueue:有界队列(需指定容量),推荐使用,避免 OOM;SynchronousQueue:无缓冲队列,任务必须立即被线程执行(无线程则创建临时线程);PriorityBlockingQueue:优先级队列,按任务优先级执行。
(4)拒绝策略(RejectedExecutionHandler)
当队列满 + 线程数达最大数时,新任务会触发拒绝策略,Java 提供 4 种默认实现:
AbortPolicy(默认):直接抛出RejectedExecutionException,中断程序;CallerRunsPolicy:由提交任务的线程(调用者)自己执行,缓解任务压力;DiscardPolicy:默默丢弃新任务,无任何提示;DiscardOldestPolicy:丢弃队列中最老的任务(队首),再尝提交新任务。
5)线程工厂(ThreadFactory)
- 用于创建线程,默认实现为
Executors.defaultThreadFactory(); - 自定义线程工厂可设置线程名称(如
"order-task-thread-1")、优先级、是否守护线程等,方便问题排查。
3.Executors工具类(常用线程池)
Java 提供java.util.concurrent.Executors工厂类,快速创建常用线程池,但实际开发中不推荐直接使用(存在 OOM 风险),仅适合简单场景。
1.FixedThreadPool(固定核心线程池)
ExecutorService fixedPool = Executors.newFixedThreadPool(5);- 特点:核心线程数 = 最大线程数(无临时线程),队列用
LinkedBlockingQueue(无界); - 适用:任务量稳定、需要控制并发度的场景(如接口异步处理);
- 风险:任务堆积过多导致 OOM。
2.CachedThreadPool(缓存线程池)
ExecutorService cachedPool = Executors.newCachedThreadPool();- 特点:核心线程数 = 0,最大线程数 =
Integer.MAX_VALUE,队列用SynchronousQueue(无缓冲),临时线程空闲 60 秒销毁; - 适用:短期、高频、耗时短的任务(如临时数据计算);
- 风险:任务过多时创建大量线程,导致 OOM 或 CPU 上下文切换爆炸。
3.SingleThreadExecutor(单线程池)
ExecutorService singlePool = Executors.newSingleThreadExecutor();- 特点:核心线程数 = 最大线程数 = 1,队列用
LinkedBlockingQueue(无界); - 适用:需要串行执行的任务(如日志写入、订单流水处理);
- 风险:任务堆积导致 OOM。
4.ScheduledThreadPool(定时/周期线程池)
- 特点:核心线程数固定,支持定时执行(
schedule())或周期性执行(scheduleAtFixedRate()); - 适用:定时任务(如每分钟刷新缓存、每天凌晨备份数据);
// 延迟2秒执行任务 scheduledPool.schedule(() -> System.out.println("延迟执行"), 2, TimeUnit.SECONDS); // 延迟1秒后,每3秒执行一次(固定频率) scheduledPool.scheduleAtFixedRate(() -> System.out.println("周期性执行"), 1, 3, TimeUnit.SECONDS);4.线程池工作原理
- 提交任务时,先判断核心线程是否已满:
- 未满:创建核心线程执行任务;
- 已满:将任务加入阻塞队列;
- 若队列已满,判断当前线程数是否达到最大线程数:
- 未达:创建临时线程执行任务;
- 已达:触发拒绝策略;
- 临时线程执行完任务后,从队列取任务,空闲超过
keepAliveTime则销毁; - 核心线程默认不会销毁,除非设置
allowCoreThreadTimeOut(true)。
任务提交 → 核心线程未满?→ 核心线程执行 → 结束 ↓ 否 队列未满?→ 加入队列 → 核心线程空闲时取任务执行 → 结束 ↓ 否 线程数达最大?→ 触发拒绝策略 → 结束 ↓ 否 创建临时线程执行 → 空闲超时销毁5.线程池状态
ThreadPoolExecutor 定义了 5 种状态(通过ctl原子变量维护,高 3 位存状态,低 29 位存线程数),状态转换不可逆:
6.任务提交方法:execute () vs submit ()
线程池提供两种提交任务的方式,核心区别在于返回值和异常处理:
1. execute()
无返回值,仅接收Runnable任务;
任务执行异常会直接抛出(需手动捕获);
executor.execute(() -> { try { // 任务逻辑 } catch (Exception e) { // 异常处理 } });2. submit()
- 有返回值(
Future对象),可接收Runnable或Callable任务; - 任务异常会被封装在
Future中,调用future.get()时才会抛出;
// 提交Callable任务(有返回值) Future<String> future = executor.submit(() -> { return "任务执行结果"; }); // 获取结果(阻塞等待任务完成) try { String result = future.get(); // 若任务异常,此处抛出ExecutionException } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }7.线程池的最佳配置实践
线程池的性能依赖参数配置,需结合业务场景(CPU 密集型 / IO 密集型)优化:
1. 核心线程数(corePoolSize)
- CPU 密集型任务(如计算、排序):核心线程数 = CPU 核心数 + 1(减少上下文切换);
- 获取 CPU 核心数:
Runtime.getRuntime().availableProcessors();
- 获取 CPU 核心数:
- IO 密集型任务(如数据库查询、网络请求):核心线程数 = CPU 核心数 * 2(或 CPU 核心数 / (1 - 阻塞系数),阻塞系数通常 0.8~0.9);
- 示例:8 核 CPU → 核心线程数 = 16。
2. 任务队列(workQueue)
- 必须使用有界队列(如
ArrayBlockingQueue),避免无界队列导致 OOM; - 队列容量根据业务峰值配置(如峰值任务量 = 1000 → 队列容量设为 1000~2000)。
3. 最大线程数(maximumPoolSize)
- IO 密集型任务:最大线程数可适当增大(如核心线程数的 2 倍),应对突发流量;
- CPU 密集型任务:最大线程数 = 核心线程数(无需临时线程,避免上下文切换)。
4. 拒绝策略(handler)
- 核心业务(如支付、下单):用
CallerRunsPolicy(调用者执行,避免任务丢失)或自定义拒绝策略(如持久化任务到 MQ / 数据库); - 非核心业务(如日志统计):用
DiscardPolicy或DiscardOldestPolicy。
5. 线程工厂(threadFactory)
- 自定义线程工厂,设置线程名称(含业务标识),方便日志排查:
ThreadFactory threadFactory = new ThreadFactory() { private final AtomicInteger count = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("order-task-thread-" + count.getAndIncrement()); thread.setDaemon(false); // 非守护线程(避免主线程退出时被强制销毁) return thread; } };6. 关闭线程池
- 程序退出时必须关闭线程池,否则核心线程会一直运行,导致 JVM 无法退出;
- 两种关闭方式:
// 1. shutdown():温和关闭,等待队列任务执行完毕,不接收新任务 executor.shutdown(); // 2. shutdownNow():强制关闭,中断正在执行的任务,返回未执行的任务 List<Runnable> unfinishedTasks = executor.shutdownNow();
8.注意事项
- 避免使用无界队列:
LinkedBlockingQueue无参构造是无界队列,任务堆积会导致 OOM; - 线程池监控:通过
ThreadPoolExecutor的getActiveCount()(活跃线程数)、getQueue().size()(队列任务数)等方法监控状态,及时调整参数; - 任务超时控制:用
Future.get(timeout, unit)设置任务超时时间,避免线程阻塞; - 异常捕获:
execute()提交的任务必须手动捕获异常,否则线程会被销毁(核心线程会重新创建,但影响效率); - 禁止使用 Executors 创建线程池:阿里 Java 开发手册明确禁止,推荐自定义
ThreadPoolExecutor。