京东面试官:线程池用过吗都有什么参数?底层如何实现的?
今天咱们聊聊Java面试中最常见的话题之一——线程池。相信大部分程序员都在面试中遇到过“你用过线程池吗?”这样的提问,对吧?
很多时候,这个问题并不只是想听你说你知道线程池的用法,而是更想知道你是否理解线程池的底层实现原理,以及它的各种参数配置。
线程池用过吗?我猜你肯定用过!
先说说线程池到底是啥。咱们做程序员的,应该都知道线程池是用来管理和复用线程的,避免频繁创建和销毁线程造成性能浪费。用线程池的好处就像用打车软件,明明有很多司机,你只需要预约一下,就能迅速得到服务,不用每次都等到有需求了才去找一个司机。而且司机不再是一个一个单独找,大家有一个统一的“管理平台”,就是线程池。
那么,在Java中,线程池的实现类是ExecutorService,它定义了线程池的基础功能,比如提交任务、关闭线程池等。而具体的线程池实现则是ThreadPoolExecutor,它控制了线程池的工作原理和行为。
线程池的几个核心参数
Java的线程池配置通常都通过ThreadPoolExecutor来实现。你可能在代码中见过类似这样的线程池初始化:
ExecutorService executor = new ThreadPoolExecutor(
4, // corePoolSize
10, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // time unit
new LinkedBlockingQueue<Runnable>(100) // workQueue
);
这里面的参数其实挺多的,每一个都关系到线程池的性能和稳定性。下面我来一一解析:
corePoolSize(核心线程数):这是线程池中始终保持活跃的线程数。即使线程池中的任务队列为空,核心线程仍然会保持活动状态。
举个例子:假设你开了一家餐馆,核心线程就相当于你这家餐馆的厨师,始终在厨房忙碌,即使没有客人来,厨师也会一直在,随时准备好迎接客人的点单。
maximumPoolSize(最大线程数):当线程池中的线程数达到核心线程数,但队列中任务仍然没有处理完时,线程池会继续创建新的线程,直到达到这个最大线程数。也就是说,当任务堆积得很严重时,线程池会增加临时线程来处理这些任务,但最大线程数也有限制。
举个例子:假设你的餐馆原本有3个厨师,这就是核心线程数。当外面客人特别多,等位队伍排得像长蛇阵,餐馆决定临时增加厨师的数量,但最多只能招到10个。
keepAliveTime(线程空闲时间):当线程池中的线程数超过了核心线程数,如果这些线程在
keepAliveTime时间内没有任务执行,线程池就会回收这些空闲线程。也就是说,不再需要的线程会被销毁掉,避免资源浪费。举个例子:厨师闲着没事做,等了60秒还没接到新订单,那么这位厨师就可以下班休息,直到有新订单来。
TimeUnit(时间单位):
keepAliveTime的时间单位,常见的单位有SECONDS、MILLISECONDS等。workQueue(工作队列):任务提交到线程池时,会先进入到一个队列。队列的类型可以有很多种,常用的有:
LinkedBlockingQueue:这是一个基于链表的阻塞队列,队列大小可以指定(上面示例中的100),如果队列满了,线程池会创建新的线程,直到最大线程数为止。ArrayBlockingQueue:一个基于数组的阻塞队列,队列的大小是固定的。SynchronousQueue:每个插入操作必须等待一个删除操作,队列的容量是0,适合非常高并发的场景。
ThreadFactory(线程工厂):线程池用来创建新线程的工厂。如果不指定,默认使用Executors.defaultThreadFactory()来创建线程。可以自定义线程工厂,方便设置线程名称或设置线程的优先级。
RejectedExecutionHandler(拒绝策略):当线程池和队列都满了,任务提交者还会继续提交任务,线程池就需要采取一个策略来处理这些任务。常见的拒绝策略有:
AbortPolicy:直接抛出RejectedExecutionException异常(默认策略)。CallerRunsPolicy:调用任务的run()方法,交给调用者线程来执行任务。DiscardPolicy:直接丢弃任务,不抛出异常。DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新尝试提交任务。
线程池的底层实现
说到这里,相信大家对线程池的基本配置已经有了一个大概的了解。那么,线程池的底层是如何实现的呢?
ThreadPoolExecutor底层是基于几个核心组件来管理线程的:
工作队列(workQueue):这个队列用于缓存待执行的任务。队列满了后,
ThreadPoolExecutor会根据配置决定是否创建新的线程。线程池(线程池中的线程):线程池中的线程由
ThreadPoolExecutor管理,线程池会根据当前的线程池大小、队列状态等,动态调整线程的数量。线程工厂(ThreadFactory):负责创建线程对象,
ThreadPoolExecutor会通过它来生成新线程。我们可以自定义线程工厂来实现一些特殊的需求,比如设置线程的名字、优先级等。拒绝策略(RejectedExecutionHandler):当队列已满且线程池中的线程都在忙碌时,
ThreadPoolExecutor会根据拒绝策略来处理新提交的任务。默认情况下会抛出异常。
一个典型的工作流程是这样的:
任务提交到线程池。 如果线程池中有空闲线程,则直接分配任务给线程池中的空闲线程。 如果线程池没有空闲线程并且队列没有满,那么任务就被加入到队列中。 如果线程池已经达到最大线程数,并且队列也满了,线程池会根据拒绝策略来处理任务。
代码示例
让我们看一段简单的代码,来感受一下线程池的使用:
import java.util.concurrent.*;public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建一个线程池,核心线程数为2,最大线程数为4,队列容量为10
ExecutorService executor = new ThreadPoolExecutor(
2, 4, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10)
);
// 提交一些任务
for (int i = 0; i < 15; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running in thread: " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
这段代码展示了如何创建一个ThreadPoolExecutor并提交一些任务。你可以看到,线程池的大小是动态变化的,它会根据任务的数量和队列的状态自动调整线程数。
如果你有更多问题,欢迎在评论区和我讨论。
-END-
以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言。