线程池面试题

一:什么是线程池?为什么要使用线程池?

是一个管理线程的池子,它可以容纳多个线程,其中的线程可以反复利用,省去了频繁创建线程对象的操作。
好处:(1)降低资源消耗,降低频繁创建、销毁线程带来的额外开销。
(2)降低使用复杂度。将任务的提交和执行进行解耦,我们只需要创建一个线程池,然后往里面提交任务就行,具体执行流程由线程池自己管理。

二:ThreadPoolExecutor 都有哪些核心参数?

corePoolSize 核心线程数目 (最多保留的线程数)
maximumPoolSize 最大线程数目
keepAliveTime 生存时间 - 针对救急线程
unit 时间单位 - 针对救急线程
workQueue 阻塞队列
threadFactory 线程工厂 - 可以为线程创建时起个好名字
handler 拒绝策略
在创建线程池后,等待提交过来的任务请求,当调用execute()方法添加任务时,线程池会做如下判断:
(1)当任务数没有超过 coreSize 时,新建一个线程来处理提交的任务;
(2)如果任务数超过 coreSize 时,加入任务队列暂存;
(3)如果任务队列满了且正在运行的线程数量小于最大线程数,那么创建一个非核心线程立刻运行这个任务;
(4)如果任务队列满了且正在运行的线程数量大于或等于最大线程数,线程池会执行拒绝策略;
上述执行流程是 JUC 标准线程池提供的执行流程,主要用在 CPU 密集型场景下。像 Tomcat,他们内部的线程池主要用来处理网络 IO 任务的,它利用 TaskQueue 的 offer() 方法修改了 JUC 线程池的执行流程,来支持 IO 密集型场景使用。

三:你刚也说到了 Worker 继承 AQS 实现了锁机制,那 ThreadPoolExecutor 都用到了哪些锁?为什么要用锁?

在这里插入图片描述

  1. mainLock 锁:ThreadPoolExecutor 内部维护了 ReentrantLock 类型锁 mainLock, workers 变量用的 HashSet 是线程不安全的,largestPoolSize(这个字段是用来记录线程池中,曾经出现过的最大线程数)、completedTaskCount 也是没用 volatile 修饰,所以需要在锁的保护下进行访问。
    在这里插入图片描述
    (1)面试官:为什么不直接用个线程安全容器呢?
    interruptIdleWorkers() 方法,用 mainLock 来实现串行化,避免中断风暴的风险,有了锁这个大前提后,也不需要并发安全的 Set 集合。
    (2)面试官:怎么理解这个中断风暴呢?
    就是如果不加锁,interruptIdleWorkers() 方法在多线程访问下就会发生这种情况。一个线程调用interruptIdleWorkers() 方法对 Worker 进行中断,此时该 Worker 出于中断中状态,此时又来一个线程去中断正在中断中的 Worker 线程,这就是所谓的中断风暴。
    (3)面试官:那 largestPoolSize、completedTaskCount 变量加个 volatile 关键字修饰是不是就可以不用 mainLock 了?
    在这里插入图片描述
    假设 addWorkers 方法还没来得及修改 largestPoolSize 的值,就有线程调getLargestPoolSize 方法。由于没阻塞,获取到的值,只是那一瞬间的 largestPoolSize,不一定是addWorker 方法执行完成后的值,所以是为了保证这两个参数的准确性,在获取这两个值时,能保证获取到的一定是addWorkers方法执行完成后的值。

  2. Worker 线程锁:该锁主要是用来维护运行中线程的中断状态。
    在这里插入图片描述
    面试官:这个维护运行中线程的中断状态怎么理解呢?
    Worker 继承 AQS 主要就是为了实现了一把非重入锁,维护线程的中断状态,保证不能中断运行中的线程。

四:你在项目中是怎样使用线程池的?Executors 了解吗?

Executors 创建的线程池有发生 OOM 的风险,Executors.newFixedThreadPool 创建的线程池内部使用的是无界的队列,可能会堆积大量请求,导致 OOM。
在 Spring 环境中使用线程池,直接使用 JUC 原生 ThreadPoolExecutor 有个问题,Spring 容器关闭的时候可能任务队列里的任务还没处理完,有丢失任务的风险。所以使用 Spring 提供的ThreadPoolTaskExecutor,并且要按业务类型进行线程池隔离,任务执行参差不齐,避免共享一个线程池。

五:刚你说到了通过 ThreadPoolExecutor 来创建线程池,那核心参数设置多少合适呢?

  1. 如果是CPU密集型应用,则线程数设置为N+1,CPU利用率达到100%,那么线程数就是CPU核心数,+1是为了确保在线程暂停时有一个额外的线程来保持CPU周期工作。所以N+1确实是一个经验值。
  2. 如果是IO密集型应用,则线程数设置为2N+1,对于IO密集型应用,假定所有的操作时间几乎都是IO操作耗时,那么W/C的值就为1,那么对应的线程数确实为2N。
  3. 如果不知道是IO密集还是CPU密集呢
    在这里插入图片描述
    上述公式很难获取准确的等待时间和计算时间,需要通过压测不断的动态调整线程池参数,观察 CPU 利用率、系统负载、GC、内存、RT、吞吐量等各种综合指标数据,来找到一个相对比较合理的值。

六:execute() 提交任务和 submit() 提交任务有啥不同?

  1. execute() 无返回值,submit() 有返回值,会返回一个 FutureTask,然后可以调用 get() 方法阻塞获取返回值。
  2. execute只能提交Runnable类型的任务,submit既能提交Runnable类型任务也能提Callable类型任务。
  3. execute会直接抛出任务执行时的异常,submit会吃掉异常,但是调用Future.get()方法时,可以捕获到异常。

七:线程池拒绝策略有哪些?

AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务。

八:为什么不建议直接使用Spring的@Async

当我们没有自定义线程池的时候,@Async就会用SimpleAsyncTaskExecutor这个线程池,其实他并不是真的线程池,它是不会重用线程的,每次调用都会创建一个新的线程,也没有最大线程数设置。所以,我们应该自定义线程池来配合@Async使用,而不是直接就用默认的。

九:你在使用线程池的过程中遇到过哪些坑或者需要注意的地方?

  1. OOM 问题,刚开始使用线程都是通过 Executors 创建的,这种方式创建的线程池会有发生 OOM 的风险。
  2. 共享线程池问题。整个服务共享一个全局线程池,导致任务相互影响,耗时长的任务占满资源,耗时短的任务不到执行。同时如果任务之间存在父子关系,可能会导致死锁的发生,进而引发 OOM。
  3. 配合ThreadLocal 使用,导致脏数据问题。我们知道 Tomcat 利用线程池来处理收到的请求,会复用线程,如果我们代码中用到了 ThreadLocal,在请求处理完后没有去 remove,那每个请求就有可能获取到之前请求遗留的脏值。
  4. 需要自定义线程工厂指定线程名称,不然发生问题都不知道咋定位。

十:什么是压测,怎么做压测?

压测通过模拟用户请求,帮助我们发现系统的瓶颈以及评估系统的整体水位。以下是进行压测的一般步骤:

  1. 确定测试目标:具体是哪个接口,那个方法,哪种具体的case。以及这次压测我们要实现什么目的。
  2. 制定压测计划:确定压测的具体策略,包括测试的时间、持续多久、并发量要压到多少
  3. 创建环境并准备脚本:压测可以在测试环境也可以在生产环境,并且需要准备好压测数据及脚本。
  4. 执行压测:根据测试计划,执行压测并收集性能指标。
  5. 监控系统性能:在施压过程中,观察系统的整体情况。包括但不限于:a.RT、CPU利用率、Load、内存情况、GC次数、GC时长、网络I0情况、堆内存情况、线上报警情况等。
  6. 分析结果:对压测结果进行分析,确定系统的性能瓶颈和潜在问题
  7. 优化和再测试:根据分析结果,进行必要的优化和改进,并重新进行压测
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答:Java线程池Java中的一个重点知识,并且在Java的工作中经常会遇到,因此在面试中也是必问的面试题目。以下是一些常见的Java线程池面试题: 1. 谈谈什么是线程池? 2. 为什么要使用线程池? 3. 你们哪些地方会使用到线程池? 4. 线程池有哪些作用? 5. 线程池的创建方式有哪些? 6. 线程池底层是如何实现复用的? 7. ThreadPoolExecutor核心参数有哪些? 8. 线程池创建的线程会一直在运行状态吗? 9. 为什么阿里巴巴不建议使用Executors? 10. 线程池的底层实现原理是什么? 11. 线程池队列满了,任务会丢失吗? 12. 线程池的拒绝策略类型有哪些? 13. 线程池如何合理配置参数? 这些问题涵盖了线程池的基本概念、使用场景、实现原理以及相关的配置和策略等方面的知识。了解这些问题能够帮助面试者更好地理解和应用Java线程池。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [java线程池面试题有哪些?java线程池常见面试题](https://blog.csdn.net/muli525/article/details/123553744)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [(一)【Java精选面试题线程池底层实现原理(含答案)](https://blog.csdn.net/qq_30999361/article/details/124924343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值