线程池是一种池化的技术,其核心思想是为了减少每次获取和结束资源的消耗,提高对资源的利用率。
线程池的基本概念
- 线程重用:线程池中的线程在执行完任务后不会被销毁,而是可以被重新利用来执行新的任务。
- 任务队列:当所有线程都在忙碌时,新的任务会被放入一个队列中等待执行。
- 线程管理:包括线程的创建、销毁、任务分配等。
线程池的好处
- 降低资源消耗:通过重复利用现有的线程来执行任务,减少了线程创建和销毁的开销。
- 提高响应速度:线程的创建是需要时间和资源的,利用现有线程执行任务,省去了创建线程的时间,拿到任务后可以立刻执行。
- 提高线程的可管理性:线程是一种稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
线程池的使用
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
,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。