线程池
线程池的基本概述
线程池是一种对线程进行复用和管理的机制。在 Java 中,通过线程池来控制线程的数量和线程的状态,提高线程的执行效率,避免频繁创建和销毁线程带来的资源消耗。就像餐厅的传菜员。餐厅有很多顾客下的订单(任务),而传菜员(线程)负责将菜送到顾客那里。如果每个订单都单独找一个传菜员,会导致传菜员数量过多,餐厅资源紧张。所以餐厅会安排固定的传菜员团队(线程池),他们负责不断地在厨房和顾客之间来回送菜(执行任务),提高服务效率。
线程池的状态
状态转换如图所示:
线程池的简单使用流程
简单的使用流程可以总结为:任务提交 → 核心线程执行 → 任务队列缓存 → 非核心线程执行 → 拒绝策略处理
第一步,创建线程池。
第二步,调用线程池的 execute()
方法,准备执行任务。
- 如果正在运行的线程数量小于 corePoolSize,那么线程池会创建一个新的线程来执行这个任务;
- 如果正在运行的线程数量大于或等于 corePoolSize,那么线程池会将这个任务放入等待队列;
- 如果等待队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么线程池会创建新的线程来执行这个任务;
- 如果等待队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会执行拒绝策略。
第三步,线程执行完毕后,线程并不会立即销毁,而是继续保持在池中等待下一个任务。
第四步,当线程空闲时间超出指定时间,且当前线程数量大于核心线程数时,线程会被回收。
执行流程图如下所示:
简单代码示例
1 | class ThreadPoolDemo { |
线程池的七个参数
ThreadPoolExecutor
的构造方法如下所示:
1 | public ThreadPoolExecutor( |
什么?白雪公主和他的七个小矮人?有点意思哈。下面讲讲每个参数的含义:
corePoolSize 核心线程数目
含义:线程池中始终保留的最小线程数,即使这些线程处于空闲状态也不会被回收(除非调用
allowCoreThreadTimeOut(true)
)
作用:保证在并发请求数少于等于该值时,所有任务都能立即得到线程执行,无需排队maximumPoolSize 最大线程数目 核心线程数+救急线程数
含义:线程池允许的最大线程数
作用:当活动线程数达到corePoolSize
且任务队列已满时,会继续创建新线程直至达到maximumPoolSize
;超过该阈值的新任务则 按拒绝策略处理keepAliveTime 救急线程的存活时间
含义:当线程数量超过核心线程数目
时,多余的线程在空闲超过此时长后会被回收
作用:控制线程池在负载低时的资源释放;unit
与之配合指定时间单位unit 时间单位,针对救急线程——–控制救急线程生存时间对的单位
含义:keepAliveTime
参数的时间单位,取值为TimeUnit
枚举
常用:TimeUnit.SECONDS(秒)
、TimeUnit.MILLISECOND
(毫秒)workQueue 阻塞队列
含义:用于缓存提交但尚未执行的任务的阻塞队列,类型为BlockingQueue<Runnable>
作用:当线程数达到corePoolSize
后,新任务会进入该队列等待空闲线程threadFactory 线程工厂—-可以为线程创建时起个好听的名字?
含义:用于创建新线程的工厂接口,默认实现可通过Executors.defaultThreadFactory()
获取
作用:可定制线程名、是否守护线程、优先级等handler 拒绝策略
含义:当线程池饱和(即线程数达maximumPoolSize
且队列已满)时,对新提交任务的处理策略
常见策略:AbortPolicy
:抛出RejectedExecutionException
(默认)CallerRunsPolicy
:由调用线程执行该任务DiscardPolicy
:直接丢弃任务DiscardOldestPolicy
:丢弃队列中最旧的一个任务,再尝试提交当前任务
一些代码示例
1 | // 1. 固定大小线程池 |
每个参数之间关系就是:任务优先使用核心线程执行,满了进入等待队列,队列满了启用非核心线程备用,线程池达到最大线程数量后触发拒绝策略,非核心线程的空闲时间超过存活时间就被回收
线程池的阻塞队列
ArrayBlockingQueue
- 特性:基于数组、有界(需在构造时指定容量)、采用 FIFO 原则
- 使用场景:希望严格控制队列大小,避免内存占用过高
LinkedBlockingQueue
- 特性:基于链表,可选有界或无界(默认容量为
Integer.MAX_VALUE
),FIFO。 - 使用场景:任务生产、消费速度不确定时,可使用无界队列以防过早拒绝。
- 特性:基于链表,可选有界或无界(默认容量为
PriorityBlockingQueue
- 特性:基于堆,无界,按优先级(
Comparator
)排序,非 FIFO - 使用场景:需要优先执行高优先级任务时
- 特性:基于堆,无界,按优先级(
SynchronousQueue
- 特性:零容量队列,生产者线程插入必须等待消费者线程取走才能继续。
- 使用场景:
newCachedThreadPool()
使用,适合执行大量短生命周期任务,不保留队列
1 | BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(50); |
Executors提供的一些工厂方法:
- newFixedThreadPool
- newCachedThreadPool
- newSingleThreadExecutor
线程池提交任务—-submit与execute
execute 方法没有返回值,适用于不关心结果和异常的简单任务
1 | threadsPool.execute(new Runnable() { |
submit 有返回值,适用于需要获取结果或处理异常的场景
1 | Future<Object> future = executor.submit(harReturnValuetask); |
线程池的关闭
可以调用线程池的shutdown
或shutdownNow
方法来关闭线程池
调用shutdown
方法,线程池的状态变为SHUTDOWN
①不会再接收新的任务
②但是已经提交了的任务会执行完
③shutdown方法不会阻塞调用线程的执行
调用shutdownNow
方法,线程池的状态变为STOP
①不会接收新任务
②会将队列中的任务返回
③并用 interrupt 的方式中断正在执行的任务
注意shutdownNow
不会真正终止正在运行的任务,只是给任务线程发送 interrupt 信号,任务是否能真正终止取决于线程是否响应 InterruptedException
线程池的异常处理
1、try-catch直接捕获处理异常
1 | executor.execute(() -> { |
2、使用 Future 获取异常
1 | Future<Object> future = executor.submit(() -> { |
3、自定义 ThreadPoolExecutor
重写 afterExecute
方法
1 | ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS, |
4、使用 UncaughtExceptionHandler·
捕获异常
1 | ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS, |
如果不关心任务返回值,建议使用 UncaughtExceptionHandler
如果关心任务返回值,建议使用 Future
如果想要全局捕获所有任务异常,建议重写 afterExecute
方法
来点面试题
解释 ThreadPoolExecutor
的七个参数及其作用。
当核心线程数已满且队列已满时,新任务如何处理?
LinkedBlockingQueue
与 SynchronousQueue
的区别及适用场景。
如何自定义线程工厂与拒绝策略?请给出代码示例。
为什么要使用线程池而不是直接创建新线程?
如何监控线程池运行状态(如活跃线程数、队列大小、已完成任务数)?
allowCoreThreadTimeOut(true)
有何作用?
请简述实现一个简单线程池的思路。
在高并发场景下,如何调优 corePoolSize
、maximumPoolSize
和队列容量?线程池如何调优
Executors.newFixedThreadPool()
与 new ThreadPoolExecutor()
在底层有何区别?