前言
在 Java 并发编程中,线程池是优化线程资源管理、提升程序性能的核心技术。无论是日常开发中的任务调度,还是框架底层的并发控制(如 Spring、Netty),线程池都发挥着不可替代的作用。本文将全面解析线程池的核心知识,帮你从理论到实践彻底掌握线程池的使用与原理。
1.什么是线程池?
线程池是一种基于“池化”思想设计的线程管理工具,实现对线程的复用和管理。它可以通过在运⾏期间维护若⼲个线程,避免了频繁创建和销毁线程带来的性能开销,同时能够控制并发线程数量,提高系统资源的利用率和响应速度。
2.线程池的核心作用
-
降低资源消耗:复用已创建的线程,减少频繁创建线程和销毁线程的开销。
-
方便线程管理:可以统⼀的进⾏分配资源、调优和监控,控制最⼤并发数。
-
提高响应速度:任务到达时无需等待线程创建,可立即直接执行。
-
提供拒绝机制:当任务过多时,可通过拒绝策略避免系统过载。
3.线程池的核心接口,实现类和工具类

1.核心接口
1.Executor:线程池的顶层接口,仅定义了任务提交方法 execute(Runnable command),
用于提交无返回值的任务,是所有线程池的基础。
2.ExecutorService:继承自 Executor
,扩展了线程池管理方法,如 submit()
(提交有返回值任务)、shutdown()
(关闭线程池)、shutdownNow()(立即关闭线程池)、awaitTermination()
(等待关闭完成)等,是实际开发中使用的主要接口。
3.ScheduledExecutorService:继承自 ExecutorService
,新增定时任务调度方法,如 schedule()
(延迟执行)、scheduleAtFixedRate()
(固定频率执行)、scheduleWithFixedDelay()(固定延迟执行)等,支持定时 / 周期性任务。
2.线程池实现类
1.ThreadPoolExecutor:最常用的线程池实现类,通过 “核心线程 + 非核心线程 + 阻塞队列 + 拒绝策略” 的组合,实现标准的线程池功能。支持自定义核心参数,灵活适配不同业务场景。
2.ScheduledThreadPoolExecutor:ThreadPoolExecutor
的子类,专门用于定时 / 周期性任务。其核心是使用 DelayedWorkQueue
(按任务执行时间排序的优先级队列),实现任务的时间驱动调度。
3.ForkJoinPool:基于分治思想的线程实现类,通过分叉(fork)合并(join)的⽅式,将⼀个⼤任务拆分成多个⼩任务,为每个工作线程分配独立队列减少竞争,实现并⾏的线程任务执⾏⽅式,适合计算密集型场景,是ThreadPoolExecutor线程池的⼀种补充。
3.工具类:Executors
Executors
是线程池的工具类,封装了常见场景的线程池配置,通过静态方法快速创建预定义参数的线程池实例,简化开发。
1.newFixedThreadPool(int nThreads)
:创建固定大小线程池(通过固定数量的线程,避免资源耗尽)
2.newCachedThreadPool()
:创建可缓存的线程池(根据任务量自动伸缩线程数量,高效处理短期,突发性的并发任务)
3.newSingleThreadExecutor()
:创建单线程线程池(串化任务,保证顺序性)
4.newScheduledThreadPool(int corePoolSize)
:创建定时任务线程池(提供可靠、高效的定时任务调度能力)
⚠️ 注意:实际开发中推荐使用
ThreadPoolExecutor
手动配置参数,避免Executors
可能导致的资源耗尽等问题。
补充:可能很多小伙伴不是很能理解实现类和工具类,下面通过对比说明了这两者的区别,以及平常如果被问到线程池的分类,我们可以从实现类和工具类这两个角度来进行回答。
1.实现类和工具类的区别
维度 | 实现类(如 ThreadPoolExecutor ) | 工具类(如 Executors ) |
---|---|---|
核心职责 | 实现线程池核心逻辑(任务调度、线程管理、拒绝策略等) | 提供预配置的线程池实例,简化创建流程 |
灵活性 | 高度灵活,支持自定义核心参数(线程数、队列、策略等) | 灵活性低,仅支持固定配置,参数不可自定义 |
适用场景 | 需精细调优的场景(高并发、资源受限、特殊业务需求) | 简单场景或快速开发(临时任务、低并发、原型开发) |
底层依赖 | 无依赖,是线程池功能的基础实现 | 依赖实现类,返回值本质是 ThreadPoolExecutor 实例 |
风险控制 | 可手动配置有界队列和拒绝策略,避免资源耗尽 | 部分方法使用无界队列(如 newFixedThreadPool ),高并发下可能导致 OOM(内存溢出) |
2.线程池的分类,从实现类来说分为3种,从工具类来说分为4种
按实现类划分(3 种):
ThreadPoolExecutor
:标准线程池,适用于绝大多数并发场景。ScheduledThreadPoolExecutor
:定时任务线程池,适用于时间驱动的任务。ForkJoinPool
:分治线程池,适用于计算密集型任务。
按工具类 Executors
划分(4 种):
FixedThreadPool
:固定大小线程池。CachedThreadPool
:可缓存线程池。SingleThreadExecutor
:单线程线程池。ScheduledThreadPool
:定时任务线程池。
4.ThreadPoolExecutor线程池的配置参数
1.核心配置参数
1.corePoolSize:线程池核心线程数,及线程池中保持的最小线程数,即使这些线程处于空闲状态也不会被回收。
2.maximumPoolSize:线程池最大线程数(核心线程数+非核心线程数),当任务队列满且核心线程繁忙时,会创建非核心线程处理任务,非核心线程空闲超过 keepAliveTime
会被回收。
3.keepAliveTime:非核心线程的空闲存活时间,超过该时间的空闲非核心线程会被销毁,释放资源。
4.TimeUnit:keepAliveTime参数的时间单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等。
5.BlockingQueue:工作队列,用于缓存待执行任务的阻塞队列,当核心线程繁忙时,新任务会先进入队列等待。
6.ThreadFactory:线程工厂,用于创建新线程的工厂,可以设置线程名称、优先级、守护线程状态等。
7.RejectedExcutionHandler:拒绝策略,当线程池中的线程耗尽,并且工作队列达到已满时,新提交的任务,将使用拒绝策略进行处理。
2.任务队列类型
队列类型 | 特点 | 适用场景 |
---|---|---|
ArrayBlockingQueue | 基于数组实现的有界队列,创建时需指定容量(如 new ArrayBlockingQueue(100) ),读写线程互斥。 | 资源受限场景,需控制队列大小避免 OOM |
LinkedBlockingQueue | 基于链表实现的有界/无界队列,默认无界,(容量为 Integer.MAX_VALUE ),也可指定容量;读写线程可并发操作。 | 任务量稳定的场景,需注意无界队列的 OOM 风险 |
SynchronousQueue | 同步队列,不缓存任务(任务提交后需立即有线程接收),吞吐量高。 | 任务处理快、提交频繁的场景(如 CachedThreadPool ) |
PriorityBlockingQueue | 优先级队列,按任务优先级排序执行(任务需实现 Comparable 接口)。 | 任务有优先级区分的场景 |
3.线程池的拒绝策略
当线程池无法处理新任务(线程数达到最大且队列满)时,触发拒绝策略。Java 默认提供 4 种策略:
拒绝策略类型 | 行为描述 | 适用场景 |
---|---|---|
AbortPolicy | 丢弃任务并抛出 RejectedExecutionException 异常(默认策略)。 | 核心任务,需明确感知任务丢失的场景 |
DiscardPolicy | 直接丢弃任务,不抛出异常。 | 非核心任务,丢失不影响主流程的场景 |
DiscardOldestPolicy | 丢弃队列中最旧的任务(队头任务),然后重新提交当前任务。 | 任务可覆盖、旧任务优先级低的场景 |
CallerRunsPolicy | 由提交任务的线程(调用者)自行执行任务,减缓任务提交速度(天然限流)。 | 需避免任务丢失,且允许调用者线程阻塞的场景 |
此外,可通过实现 RejectedExecutionHandler
接口自定义拒绝策略(如将任务持久化到数据库、异步重试等)。
4.线程池参数设置策略
1.核心线程数(corePoolSize)
·CPU密集型任务:线程数设置为CPU核心数+1(避免上下文切换,充分利用CPU计算资源)
·IO密集型任务:线程数设置为2*CPU核心数(I0操作会导致线程阻塞,需要更多线程处理其他任务)
2.最大线程数(maximumPoolSize):maximumPoolSize保持与corePoolSize相同
3.任务队列(workQueue)
·无界队列(如LinkedBlockingQueue):适用于任务量较小且稳定的场景。
(注意:可能导致OOM)
·有界队列(如ArrayBlockingQueue):必须设置合理的队列容量,结合maximumPoolSize控制总并发数。
(注意:可以避免资源耗尽)
4.空闲线程存活时间(keepAliveTime)
·IO密集型任务:可设置较长的存活时间(如60秒),避免频繁创建和销毁线程。
·CPU密集型任务:可设置较短的存活时间,及时释放资源。
(通过allowCoreThreadTimeOut(true)可使核心线程也受keepAliveTime控制)
5.拒绝策略(RejectedExecutionHandler)
·AbortPolicy(默认):抛出RejectedExecutionException,适合关键任务。
·CallerRunsPolicy:由提交任务的线程执行,可降低提交速度。
·DiscardPolicy:直接丢弃任务,适合允许丢失的非关键任务。
·DiscardoldestPolicy:丢弃队列中最老的任务,尝试重新提交当前任务。
·自定义策略:实现RejectedExecutionHandler接口,如将任务持久化到数据库、异步重试等。
实际建议:
-
不要盲目使用无界队列,可能导致内存溢出
-
合理设置线程空闲时间,及时回收多余线程
-
根据实际监控数据动态调整参数
-
使用有意义的线程名称,便于问题排查
5.线程池的执行流程
- 提交任务:通过
execute()
或submit()
方法向线程池提交任务。 - 检查空闲核心线程:若存在空闲核心线程,直接分配线程执行任务。
- 核心线程数未满:若无可用空闲核心线程,且当前工作线程数 <
corePoolSize
,创建新核心线程执行任务。 - 任务队列未满:若核心线程数已满,将任务放入
workQueue
等待,待线程空闲后从队列中取任务执行。 - 最大线程数未满:若队列已满,且当前工作线程数 <
maximumPoolSize
,创建非核心线程执行任务。 - 执行拒绝策略:若队列已满且工作线程数达
maximumPoolSize
,则使用拒绝策略处理该任务
6.线程池常见的方法
1.线程池的创建
1.手动创建
public class Demo { public static void main(String[] args) throws ExecutionException, InterruptedException { ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, // corePoolSize 10, // maximumPoolSize 60, // keepAliveTime TimeUnit.SECONDS, // TimeUnit new ArrayBlockingQueue<>(100), // BlockingQueue new ThreadPoolExecutor.CallerRunsPolicy() // RejectedExecutionHandler ); } }
2.使用Executors工具类:
public class Test03 { public static void main(String[] args) { //1.固定大小线程池(自定义固定线程数的线程池) ExecutorService fiexedThreadPool = Executors.newFixedThreadPool(5); //2.单线程线程池(队列,保证进来的任务有顺序的,可按顺序执行) ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); //3.缓存线程池(同步队列(快速执行任务),60s的存活时间提高线程复用率) ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //4.定时任务线程池(一般固定时间都按照每周一,每个月月底等,这种自定的使用很少) ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); } }
2.执行线程任务
1.执行无返回值任务
executor.execute(() -> { //任务逻辑 });
2.提交有返回值任务
Future<String> future=executor.submit(() -> { return "result"; }); String result=future.get();
3.批量执行任务
List<Callable<String>> tasks = new ArrayList<>(); //添加多个任务 //执行所有任务 List<Future<String>> futures = executor.invokeAll(tasks); //执行一个任务 String result = executor.invokeAny(tasks);
3.关闭线程
1.平缓关闭:
executor.shutdown();
2.立即关闭:
executor.shutdownNow();
3.等待终止:
executor.shutdown(); if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { //超时后强制关闭 executor.shutdownNow(); }
4.线程池监控
// 获取当前线程池中的线程数 int poolSize = executor.getPoolSize(); // 获取活跃线程数(正在执行任务的线程数) int activeCount = executor.getActiveCount(); // 获取已完成的任务数 long completedTaskCount = executor.getCompletedTaskCount(); // 获取任务队列中的任务数 int queueSize = executor.getQueue().size(); // 获取线程池曾经创建过的最大线程数 int largestPoolSize = executor.getLargestPoolSize();
7.线程池的状态
线程池使用一个 AtomicInteger 类型的变量(ctl)来同时维护两个信息:
-
线程池的运行状态(runState):高3位
-
工作线程的数量(workerCount):低29位
这种设计通过位运算实现了状态和数量的原子性操作。
-
RUNNING
(运行状态):线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。该状态的线程池会接收新任务,并处理工作队列中的任务。
·调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态;
·调用线程池的shutdownNow()方法,可以切换到STOP停止状态; -
·当工作队列为空且线程数为 0时,进入SHUTDOWN
(关闭状态):该状态的线程池不会接收新任务,但会处理工作队列中的任务。TIDYING状态
-
·当线程数为0时,进入STOP
(停止状态):该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;TIDYING状态
-
TIDYING
(整理状态):该状态所有任务已终止,线程数为 0,准备进入终止状态
·调用terminated()方法进入TERMINATED状态
5.TERMINATED
(终止状态):线程池彻底关闭,所有资源释放
8.总结
线程池是Java并发编程中的重要组件,合理使用线程池可以带来诸多好处:
-
提升性能:通过线程复用减少创建销毁开销
-
提高响应速度:任务到达时立即有线程可用
-
资源管控:防止无限制创建线程导致系统崩溃
-
功能丰富:提供定时执行、批量执行等高级功能
最佳实践建议:
-
根据任务类型(CPU密集/IO密集)合理配置参数
-
使用有界队列避免内存溢出
-
设置合适的拒绝策略,保证系统稳定性
-
实现监控机制,及时发现和处理问题
-
考虑使用ThreadPoolExecutor手动创建,避免Executors的潜在问题
线程池的合理使用需要对业务特性和系统资源有深入的理解,通过持续监控和调优,才能发挥线程池的最大效益。希望这篇文章可以给你带来帮助,如果觉得有用点个赞关注收藏一下吧~