从零开始学 Java 线程池:ThreadPoolExecutor 基础教程
一、介绍
线程池是 Java 并发编程中核心的组件,本质是管理一组可复用线程的池化技术,目的是解决线程频繁创建 / 销毁的性能开销、控制并发线程数量、统一管理线程生命周期等问题。
补:什么是池化技术?
池化技术(Pooling Technology)是一种资源复用与管理的设计模式,核心思想是提前创建并维护一组可复用的资源对象池,当系统需要资源时直接从池中获取,用完后归还到池中而非销毁,从而避免频繁创建和销毁资源带来的性能开销。
简单来讲:池化 = 资源预创建 + 复用 + 统一管理
作用:
- 降低资源消耗:线程是重量级资源(占用栈内存、CPU 上下文),复用线程避免频繁创建 / 销毁的开销;
- 提高响应速度:任务到达时无需等待线程创建,直接复用空闲线程;
- 控制并发风险:避免无限制创建线程导致 CPU 满载、内存溢出(OOM);
- 便于统一管理:可监控线程状态、统计任务执行情况、设置超时 / 拒绝策略等。
二、原理
线程池的核心是ThreadPoolExecutor类(JDK 1.5+ 引入),其工作流程:
- 任务提交后,先判断核心线程池是否已满 → 未满则创建核心线程执行任务;
- 核心线程池满 → 判断任务队列是否已满 → 未满则将任务入队等待;
- 任务队列满 → 判断最大线程池是否已满 → 未满则创建非核心线程执行任务;
- 最大线程池满 → 触发拒绝策略处理无法执行的任务。
三、ThreadPoolExecutor参数
ThreadPoolExecutor的构造方法是理解线程池的关键,核心参数共 7 个:
publicThreadPoolExecutor(intcorePoolSize,// 核心线程数(常驻线程数,即使空闲也不会销毁)intmaximumPoolSize,// 最大线程数(线程池允许的最大线程数量)longkeepAliveTime,// 非核心线程空闲超时时间(超时后销毁)TimeUnitunit,// keepAliveTime 的时间单位(如秒、毫秒)BlockingQueue<Runnable>workQueue,// 任务等待队列(核心线程满后存放任务)ThreadFactorythreadFactory,// 线程创建工厂(自定义线程命名、优先级等)RejectedExecutionHandlerhandler// 拒绝策略(任务无法处理时的策略))关键参数说明:
- **核心线程数(corePoolSize)**线程池长期维持的线程数量,即使线程空闲也不会销毁(除非设置
allowCoreThreadTimeOut(true))。 - **最大线程数(maximumPoolSize)**线程池能创建的最大线程数,核心线程 + 非核心线程的总数不能超过此值。
- **任务队列(workQueue)**常用实现类:
ArrayBlockingQueue:有界数组队列(需指定容量,避免队列无限膨胀);LinkedBlockingQueue:无界 / 有界链表队列(默认无界,易导致 OOM);SynchronousQueue:同步队列(不存储任务,直接传递给线程,需配合大的 maximumPoolSize);PriorityBlockingQueue:优先级队列(按任务优先级执行)。
- **拒绝策略(handler)**当线程池和任务队列都满时触发,JDK 提供 4 种默认策略:
AbortPolicy(默认):直接抛出RejectedExecutionException;CallerRunsPolicy:由提交任务的线程(如主线程)执行任务;DiscardPolicy:静默丢弃任务,无任何提示;DiscardOldestPolicy:丢弃队列中最旧的任务,尝试重新提交当前任务。
四、Java 内置线程池(Executors 工具类)
JDK 提供Executors快速创建常用线程池,但生产环境不推荐直接使用(易导致 OOM 或线程数失控),需手动自定义ThreadPoolExecutor。
| 线程池类型 | 核心参数 | 适用场景 | 风险点 |
|---|---|---|---|
| FixedThreadPool | corePoolSize=maximumPoolSize,队列无界 | 执行长期稳定的任务 | 任务堆积易 OOM |
| SingleThreadExecutor | 核心 / 最大线程数 = 1,队列无界 | 串行执行任务 | 任务堆积易 OOM |
| CachedThreadPool | corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,队列同步队列 | 执行短期异步任务 | 线程数失控易 OOM |
| ScheduledThreadPool | 核心线程数固定,最大线程数 = Integer.MAX_VALUE | 执行延迟 / 周期性任务 | 任务过多易 OOM |
五、线程池使用示例(推荐自定义)
importjava.util.concurrent.*;publicclassThreadPoolDemo{publicstaticvoidmain(String[]args){// 1. 定义核心参数intcorePoolSize=Runtime.getRuntime().availableProcessors();// CPU核心数intmaximumPoolSize=corePoolSize*2;longkeepAliveTime=60L;TimeUnitunit=TimeUnit.SECONDS;// 有界队列(避免无界队列OOM)BlockingQueue<Runnable>workQueue=newArrayBlockingQueue<>(100);// 自定义线程工厂(便于排查问题)ThreadFactorythreadFactory=newThreadFactory(){privateintcount=0;@OverridepublicThreadnewThread(Runnabler){Threadthread=newThread(r);thread.setName("demo-thread-"+count++);thread.setDaemon(false);// 非守护线程thread.setPriority(Thread.NORM_PRIORITY);returnthread;}};// 自定义拒绝策略(日志+抛异常)RejectedExecutionHandlerhandler=(r,executor)->{System.err.println("任务 "+r+" 被拒绝,线程池状态:"+"核心线程数="+executor.getCorePoolSize()+",活跃线程数="+executor.getActiveCount()+",队列大小="+executor.getQueue().size());thrownewRejectedExecutionException("任务 "+r+" 执行失败");};// 2. 创建线程池ThreadPoolExecutorexecutor=newThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);// 3. 提交任务(推荐使用submit,可获取返回值/异常)for(inti=0;i<10;i++){intfinalI=i;Future<Integer>future=executor.submit(()->{System.out.println(Thread.currentThread().getName()+" 执行任务:"+finalI);returnfinalI*2;});// 获取任务结果(需处理异常)try{System.out.println("任务"+finalI+"结果:"+future.get());}catch(InterruptedException|ExecutionExceptione){e.printStackTrace();}}// 4. 关闭线程池(必须手动关闭,否则JVM不会退出)executor.shutdown();// 平缓关闭:等待已提交任务执行完毕// executor.shutdownNow(); // 强制关闭:中断所有线程,返回未执行的任务try{// 等待线程池关闭,超时则强制退出if(!executor.awaitTermination(5,TimeUnit.MINUTES)){executor.shutdownNow();}}catch(InterruptedExceptione){executor.shutdownNow();}}}六、线程池核心状态
线程池通过ctl原子变量维护状态,核心状态包括:
RUNNING:接收新任务,处理队列任务;SHUTDOWN:不接收新任务,处理队列任务;STOP:不接收新任务,不处理队列任务,中断正在执行的任务;TIDYING:所有任务执行完毕,线程数为 0,准备执行terminated()钩子;TERMINATED:terminated()执行完毕。
七、注意
- 避免使用 Executors 默认实现:手动自定义
ThreadPoolExecutor,指定有界队列和合理的核心 / 最大线程数; - 核心线程数设置:
- CPU 密集型任务(如计算):核心线程数 = CPU 核心数 + 1(减少线程切换);
- IO 密集型任务(如数据库 / 网络请求):核心线程数 = CPU 核心数 * 2(利用空闲时间);
- 监控线程池状态:通过
getActiveCount()、getQueue().size()、getCompletedTaskCount()等方法监控; - 优雅关闭线程池:使用
shutdown()+awaitTermination(),避免强制关闭导致任务丢失; - 捕获任务异常:使用
submit()提交任务,通过Future.get()捕获异常(execute()无法捕获任务内异常); - 避免任务长时间阻塞:如数据库查询设置超时,防止线程长期占用导致线程池耗尽。
八、总结
Java 线程池的核心是ThreadPoolExecutor,通过核心线程、最大线程、任务队列、拒绝策略的组合,实现线程的复用和并发控制。生产环境中需根据业务场景自定义参数,避免默认实现的坑,同时做好监控和优雅关闭,确保线程池稳定运行。
附表:
| JDK 版本 | 发布时间 | 线程池核心变化 | 关键说明 |
|---|---|---|---|
| JDK 1.5 | 2004 年 | 首次引入线程池核心 API✅ 新增java.util.concurrent包✅ 核心类:ThreadPoolExecutor、ScheduledThreadPoolExecutor✅ 工具类:Executors(提供固定 / 缓存 / 单线程池等工厂方法)✅ 核心接口:Executor、ExecutorService、ScheduledExecutorService | 线程池的 “奠基版本”,解决了手动创建线程的性能问题,定义了线程池的核心模型(核心线程、最大线程、队列、拒绝策略);Executors提供的默认实现(如newCachedThreadPool)成为主流用法,但后续被指出存在配置风险。 |
| JDK 1.6 | 2006 年 | 性能优化 + 细节补充✅ 优化ThreadPoolExecutor的线程回收逻辑✅ 新增ThreadPoolExecutor.afterExecute()方法(支持任务执行后回调)✅ 完善ScheduledThreadPoolExecutor的定时任务精度 | 无核心 API 变更,主要是性能调优和扩展性增强;回调方法的补充让线程池的监控 / 日志能力更灵活。 |
| JDK 1.7 | 2011 年 | 无核心变更 | 线程池相关 API 无新增 / 修改,仅跟随 JDK 整体的并发包优化(如 Fork/Join 框架引入,但与线程池无关)。 |
| JDK 1.8 | 2014 年 | Lambda 适配 + 小优化✅ 支持 Lambda 表达式简化任务提交(executor.submit(() -> { ... }))✅ 优化ThreadPoolExecutor的队列调度逻辑✅ 修复ScheduledThreadPoolExecutor的任务取消 bug | 核心功能无变化,主要是适配 Lambda 提升易用性;修复了定时任务中已取消任务仍可能执行的问题。 |
| JDK 9 | 2017 年 | 模块化 + 废弃部分 API✅java.util.concurrent归入java.base模块✅ 废弃Executors的newWorkStealingPool(int)重载方法(仅保留无参版)✅ 新增ExecutorService.shutdownNow()的返回值优化(更精准返回未执行任务) | 模块化调整不影响使用;废弃的newWorkStealingPool(int)因参数设计不合理被移除,无参版(基于 CPU 核心数)保留。 |
| JDK 10 | 2018 年 | 无核心变更 | 仅小范围 bug 修复,无线程池相关 API 调整。 |
| JDK 11(LTS) | 2018 年 | 性能优化 + 安全增强✅ 优化ThreadPoolExecutor的锁竞争(减少核心线程创建时的锁开销)✅ 修复ScheduledThreadPoolExecutor的内存泄漏问题✅ 新增ExecutorService的submit方法异常处理优化 | LTS 版本,核心稳定,主要是生产环境级别的 bug 修复和性能调优;解决了长期运行的定时线程池可能导致的内存泄漏问题。 |
| JDK 12 - 19 | 2019-2022 年 | 无核心 API 变更✅ 小范围 bug 修复(如线程池拒绝策略触发时机)✅ JDK 19 新增虚拟线程预览特性(不影响传统线程池) | 传统线程池 API 进入 “维护期”,无新功能;虚拟线程(Project Loom)开始预览,但属于全新的并发模型,与传统线程池并行存在。 |
| JDK 20 | 2023 年 | 虚拟线程转正(预览→孵化)✅Executors.newVirtualThreadPerTaskExecutor()(创建虚拟线程池)✅ 传统线程池 API 无变更 | 虚拟线程池成为正式孵化特性,但传统线程池仍为核心;虚拟线程池不替代传统线程池,适用于 IO 密集型场景。 |
| JDK 21(LTS) | 2023 年 | 虚拟线程正式 GA✅ 虚拟线程(VirtualThread)成为正式特性✅ 新增Executors.newVirtualThreadPerTaskExecutor()(稳定 API)✅ 传统线程池(ThreadPoolExecutor)完全兼容,无废弃 / 修改 | 里程碑版本:虚拟线程池正式可用,传统线程池仍为 “CPU 密集型任务” 的首选;核心变化:线程池分为 “平台线程池”(传统)和 “虚拟线程池”(新),按需选择。 |
注:
虚拟线程(VirtualThread) 是 JDK 21 正式纳入标准的轻量级用户态线程(属于 Project Loom 核心特性),核心是通过M:N 映射模型(M 个虚拟线程映射到 N 个操作系统内核线程)实现高效的并发处理,