面试必看!当线程池队列满了,任务会如何处理?

360影视 2025-02-03 20:29 2

摘要:在一个繁忙的互联网公司,程序员小王正在准备自己的社招面试。面试官看着简历,露出了微笑:“你知道线程池吧?”小王点点头,心想这简直是太简单的知识点了!于是他信心满满地回答:“当然知道!线程池是用来管理线程的,通过池化来避免频繁创建销毁线程的性能损耗。” 面试官眯

大家好,我是你们的朋友小米!今天给大家带来一篇关于java线程池的面试题分析,希望通过这个故事让大家更轻松地理解这个问题。

在一个繁忙的互联网公司,程序员小王正在准备自己的社招面试。面试官看着简历,露出了微笑:“你知道线程池吧?”小王点点头,心想这简直是太简单的知识点了!于是他信心满满地回答:“当然知道!线程池是用来管理线程的,通过池化来避免频繁创建销毁线程的性能损耗。” 面试官眯了眯眼:“那好,假设你在一个高并发的环境中提交了大量任务,突然队列满了,线程池接收不到任务了,怎么办?”

一听这个问题,小王顿时脑袋一懵——他从来没有遇到过这种情况!

其实,这个问题就是线程池的任务队列满了以后会发生什么? 这是很多开发者可能在实际项目中都忽视的一个细节,但在面试中却是一个常见且有挑战性的问题。那么今天,我们就一起通过这个问题来探讨一下,Java线程池的“深水区”到底有多深!

在开始分析之前,咱们还是先回顾一下线程池的基本概念,以免有小伙伴对这个概念不太熟悉。

在Java中,线程池是由java.util.concurrent.Executor接口及其实现类ThreadPoolExecutor来实现的。线程池的核心作用就是将任务提交给线程池,让线程池中的线程来执行这些任务,而不需要每次任务都创建新线程。这样就能显著提高性能,减少资源消耗。

线程池的基本组成:

核心线程数: 用来执行任务的线程数量,线程池启动时会创建这个数量的线程。最大线程数: 线程池中允许存在的最大线程数量,如果核心线程数不够,且任务队列已满,线程池会创建新的线程,直到达到最大线程数。任务队列: 用来保存待执行任务的队列。如果线程池中的核心线程都忙时,新提交的任务会先存入这个队列,等待线程空闲出来再执行。线程池的饱和策略: 当线程池队列已满,且线程池的线程数已经达到最大线程数时,任务应该如何处理。这里就涉及到我们面试题的关键点。

接下来,我们来重点讲解一下,当线程池队列满了,且没有空闲线程时,任务会如何处理? 这时就需要看线程池的饱和策略(RejectedExecutionHandler)了。

Java线程池提供了四种默认的饱和策略,分别是:

1、AbortPolicy(默认策略):

这是最常见也是最“直接”的策略。任务会被直接拒绝,并且抛出RejectedExecutionException异常。这种策略意味着,当任务队列满了,线程池的线程数也达到了最大值,新的任务就会被拒绝并抛出异常。

举个例子: 假设你提交了一个任务,而线程池的队列已经满了,且没有空闲线程可用,线程池会选择抛出RejectedExecutionException。你如果没有特别处理,就会看到这个异常,程序就会终止或进入异常处理流程。

2、CallerRunsPolicy:

这个策略比较特别。当线程池的任务队列满了并且线程池也没有空闲线程时,提交任务的线程会自己执行这个任务,而不是交给线程池来执行。也就是说,调用者线程会直接执行被拒绝的任务,而不会抛出异常。

举个例子: 你提交了一个任务,线程池的队列已经满了,且线程池的线程数也已达到最大值,线程池不会创建新线程,而是把任务“交给”提交任务的线程(即当前执行任务的线程)来执行。

这种策略可以避免任务丢失,但可能会导致调用者线程的负载过高,影响程序的整体响应能力。

3、DiscardPolicy:

采用这种策略时,如果线程池队列满了并且没有空闲线程,线程池会悄悄地丢弃这个任务,且不抛出任何异常。任务就像没提交一样,不会被执行。

举个例子: 你提交了一个任务,但线程池的队列已满,线程池没有空闲线程。这个任务就被丢弃,线程池会继续执行已有的任务。这个策略适合不要求任务一定执行的场景,但如果丢弃任务是不可接受的,就需要慎用。

4、DiscardOldestPolicy:

这种策略会丢弃队列中最旧的任务,并尝试提交当前任务。也就是说,线程池会抛弃最早提交的任务,以便为新的任务腾出位置。

举个例子: 你提交了一个任务,线程池队列已满,且没有空闲线程。线程池会删除队列中最旧的任务(即已经排队很久但还未执行的任务),然后尝试把当前提交的任务放入队列中。

选择合适的饱和策略需要结合业务场景。对于一些对任务执行时间有严格要求的业务,丢弃任务显然不可取,应该选择CallerRunsPolicy策略,让任务交给调用者线程执行。而对于一些可以容忍丢弃任务的场景,可以选择DiscardPolicy或者DiscardOldestPolicy。

总结一下:

AbortPolicy:任务拒绝,抛出异常。CallerRunsPolicy:任务交给提交任务的线程执行。DiscardPolicy:丢弃任务,不抛异常。DiscardOldestPolicy:丢弃最旧的任务,尝试提交新任务。

创建线程池时,我们可以通过ThreadPoolExecutor构造方法中的参数来指定线程池的饱和策略:

在这个构造方法中,AbortPolicy就是默认的饱和策略。如果你想使用其他的策略,可以将其替换为CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。

面试官的回答

回到小王的面试场景,面试官问完这个问题后,看到小王略显慌张的表情,笑着说道:“其实,线程池的饱和策略是非常重要的,很多开发者在高并发的场景下往往忽视了这一点,导致任务的丢失或者线程池的异常行为。所以,在选择线程池的饱和策略时,我们一定要根据具体的业务需求来决定,而不是盲目使用默认的策略。”

小王松了一口气:“原来如此,之前我并没有考虑过这个细节,谢谢您的解答!”

面试官点点头:“好的,你已经掌握了线程池的基本知识,接下来我们继续……”

来源:烨华教育

相关推荐