线程池是一种池化的技术,其核心思想是为了减少每次获取和结束资源的消耗,提高对资源的利用率。
线程池的基本概念
- 线程重用:线程池中的线程在执行完任务后不会被销毁,而是可以被重新利用来执行新的任务。
- 任务队列:当所有线程都在忙碌时,新的任务会被放入一个队列中等待执行。
- 线程管理:包括线程的创建、销毁、任务分配等。
线程池的好处
- 降低资源消耗:通过重复利用现有的线程来执行任务,减少了线程创建和销毁的开销。
- 提高响应速度:线程的创建是需要时间和资源的,利用现有线程执行任务,省去了创建线程的时间,拿到任务后可以立刻执行。
- 提高线程的可管理性:线程是一种稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
线程池的使用
public class ThreadPoolTest {
public static void main(String[] args) throws Exception {
//1. 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
5,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 2. 往线程池中提交任务
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " - Java 面试 600 讲");
});
}
// 3. 关闭线程池
threadPoolExecutor.shutdown();
}
}
执行结果:
从上面可以看出,线程池的使用非常简单:
- 调用
new ThreadPoolExecutor()
构造方法,指定核心参数,创建线程池。 - 调用
execute()
方法提交 Runnable 任务。 - 使用结束后,调用
shutdown()
方法,关闭线程池。
对于线程池而言,其核心参数非常重要,这也是我们面试线程池必问的一个问题。
线程池核心参数
线程池共有七大核心参数:
参数名称 | 参数含义 |
---|---|
int corePoolSize | 核心线程数 |
int maximumPoolSize | 最大线程数 |
long keepAliveTime | 线程存活时间 |
TimeUnit unit | 时间单位 |
BlockingQueue workQueue | 阻塞队列 |
ThreadFactory threadFactory | 线程创建工厂 |
RejectedExecutionHandler handler | 拒绝策略 |
1、 corePoolSize
:核心线程数
当线程池中的线程数小于 corePoolSize
时,即使有空闲线程,线程池也会创建新线程来处理新的任务。默认情况下,空闲状态的线程是不会被回收的,所以 corePoolSize
是线程池中始终保持活跃的线程数量。但是我们可以通过一个参数 allowCoreThreadTimeOut
来控制核心线程的行为。当该参数被设置为 true 时,在空闲时间超过 keepAliveTime 时,核心线程也会被终止并回收。
举个例子:如果你有一个核心线程数为 5,最大线程数为 10 的线程池,且 allowCoreThreadTimeOut
设置为 false(默认情况),那么即使没有任务执行,线程池中也始终会有 5 个线程活跃。但如果将 allowCoreThreadTimeOut
设置为 true,那么这些核心线程在空闲超过 keepAliveTime 后会被终止回收,可能导致线程池中暂时没有任何线程。
这个参数比较适用于哪些“间歇性”的场景,即有时非常忙有时又完全空闲,在这种情况下,设置 allowCoreThreadTimeOut = true,可以在系统空闲时节约资源。
2、maximumPoolSize
:最大线程数
这是线程池中允许的最大线程数量。
当线程池中的线程数达到 corePoolSize ,阻塞队列又满了之后,才会继续创建线程,直到达到 maximumPoolSize
。另外空闲的非核心线程会被回收。
3、keepAliveTime
:线程存活时间
当线程池中线程数量超过 corePoolSize
时,这是超出部分线程在空闲时的存活时间。
如果线程空闲时间超过 keepAliveTime
,线程将被销毁,直到线程池中的线程数降至 corePoolSize
。注意,这是默认情况下。
4、unit
:时间单位
keepAliveTime
参数的时间单位,默认是TimeUnit.MILLISECONDS(毫秒),可选择的有:
- TimeUnit.NANOSECONDS(纳秒)
- TimeUnit.MICROSECONDS(微秒)
- TimeUnit.MILLISECONDS(毫秒)
- TimeUnit.SECONDS(秒)
- TimeUnit.MINUTES(分钟)
- TimeUnit.HOURS(小时)
- TimeUnit.DAYS(天)
5、workQueue
:阻塞队列
存放待处理任务的阻塞队列。当线程池中的线程数达到corePoolSize,再提交的任务就会放到阻塞队列的等待,默认使用的是LinkedBlockingQueue,可选择的有:
- LinkedBlockingQueue(基于链表实现的阻塞队列)
- ArrayBlockingQueue(基于数组实现的阻塞队列)
- SynchronousQueue(只有一个元素的阻塞队列)
- PriorityBlockingQueue(实现了优先级的阻塞队列)
- DelayQueue(实现了延迟功能的阻塞队列)
6、threadFactory
:线程创建工厂
用于创建新线程的工厂,默认的是Executors.defaultThreadFactory()。利用它,我们可以自定义线程创建的过程,例如设置线程名、优先级等。
创建线程的时候最好指定线程名称,便于排查问题。
7、handler
:拒绝策略
当线程池中的线程数达到maximumPoolSize,阻塞队列也满了之后,再往线程池中提交任务,就会触发执行拒绝策略。常见的拒绝策略有:
AbortPolicy
(直接终止,抛出异常,默认)CallerRunsPolicy
(返回给调用者执行)DiscardPolicy
(默默丢弃,不抛出异常)DiscardOldestPolicy
(丢弃队列中最旧的任务,执行当前任务)
线程池执行过程
- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
- 如果
workerCount < corePoolSize
,则创建并启动一个线程来执行新提交的任务,即便有空闲线程。 - 如果
workerCount >= corePoolSize
,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。 - 如果
workerCount >= corePoolSize && workerCount < maximumPoolSize
,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。 - 如果
workerCount >= maximumPoolSize
,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。