面试题——线程池

本文详细解析了线程池的工作原理,包括线程池的执行流程、常见线程池类型及其创建方法,探讨了线程池参数设置的策略,并介绍了线程池在实际项目中的应用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.为什么要用线程池

  • 降低资源消耗:通过复用线程,降低创建和销毁线程的损耗。
  • 提高响应速度:任务不需要等待线程创建就能立即执行。
  • 提高线程的可管理性:使用线程池可以进行统一的分配、调优和监控。

2. 线程池执行流程(原理)

ThreadPoolExecutor的执行流程(原理)(重要)

1. 如果核心线程没满,就用核心线程来处理任务。
2. 如果核心线程满了,就将任务加入到阻塞队列。
3. 如果阻塞队列满了,就创建临时线程来处理任务,临时线程的最大创建数量是max - core。
4. 如果创建的临时线程数加上核心线程数达到了线程池允许的最大线程数,新任务将被拒绝,并按照拒绝策略来处理。
5. 创建的临时线程在处理完毕后,会等待指定的keepAliveTime时间,超过这个等待时间后会释放掉这些临时线程。

  模拟 10 个任务,配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中。当前的 5 个任务执行完成后,才会执行剩下的 5 个任务。
在这里插入图片描述
面试题: 现有一个线程池,参数corePoolSize = 5,maximumPoolSize = 10,BlockingQueue阻塞队列长度为 5 :
Q:有4个任务同时进来,问:线程池会创建几条线程?
A:执行execute方法会新建 4 条线程来执行任务;
Q:又同时进来 2 个任务呢(总共6个任务了)?
A:会新建 1 条线程来执行任务,这时核心线程池满了,但还剩下1个任务,线程池会将剩下这个任务放进阻塞队列中,等待空闲线程执行。
Q:又同时进来了 5 个任务呢(总共11个任务了)?
A:线程池会继续将这5个任务放进阻塞队列,然后导致阻塞队列满了,此时核心线程也用完了,但还剩下1个任务,于是线程池会创建 1 条“临时”线程来执行这个任务。“临时”指的是他们不会长期存在于线程池,存活时间为 keepAliveTime。

3.有哪些线程池?/ 有哪几种方法创建线程池?(常见的线程池,线程池的种类,线程池的实现类,阻塞队列,缺点)

方式一:通过Executor 框架的工具类Executors来实现:

  1. Executors.newFixedThreadPool()方法返回一个线程数量固定的线程池。超过这个数量后新进来的任务会被放到等待队列中,等线程空闲时,再按先进先出的原则处理队列中的任务。
  2. Executors.newSingleThreadExecutor()方法返回一个只有一个线程的线程池,core和max均为1。超过这个数量后新进来的任务会被放到等待队列中,等线程空闲时,再按先进先出的原则处理队列中的任务。

  FixedThreadPool 和 SingleThreadExecutor 使用的都是 LinkedBlockingQueue 作为等待队列,这是一个无界队列,允许请求的队列长度为 Integer.MAX_VALUE ,所以可能堆积大量请求,导致OOM。

  1. Executors.newCachedThreadPool()方法返回一个core为0,max为Integer.MAX_VALUE的线程池。有任务进来时会创建临时线程,里面的线程都能回收。它使用的是 SynchronousQueue 作为等待队列,所以不会保留任务,直接创建临时线程来处理新任务,允许创建的线程数量为 Integer.MAX_VALUE,所以可能会创建大量线程,导致OOM。

方式二:通过构造方法 ThreadPoolExecutor 自定义参数来实现。
  

4. 线程池需要输入的参数

  • corePoolSize:表示核心线程池的大小,在线程池创建好后,这些核心线程就处于就绪状态了,即使线程池空闲,也会保留这些线程。

  • maximumPoolSize:表示线程池允许创建的最大线程数。如果阻塞队列已满,并且已创建的线程数小于最大线程数,则线程池会创建新的线程执行任务。作用是控制资源。

  • workQueue:表示阻塞队列,用于保存等待执行的任务。常用的阻塞队列有:①基于数组结构的有界阻塞队列 ArrayBlockingQueue。②基于链表结构的阻塞队列 LinkedBlockingQueue。③不存储元素的阻塞队列 SynchronousQueue。④具有优先级的阻塞队列 PriorityBlockingQueue。 阻塞队列

  • keepAliveTime:当线程池中的线程数量大于 corePoolSize 时,如果没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待新任务的到来,直到等待的时间超过了 keepAliveTime 才会被回收销毁。

  • TimeUnit:表示keepAliveTime的时间单位。

  • handler:饱和策略(拒绝策略)。当线程池的阻塞队列已满并且线程数量达到了最大线程数,就需要采用拒绝策略来处理新任务。线程池提供了四种拒绝策略:
    ①AbortPolicy,表示拒绝处理新任务并抛异常,它也是默认情况下的策略。
    ②CallerRunsPolicy:表示用调用者所在的线程来运行任务,减轻线程池的压力,但可能会导致调用者线程阻塞。如果不能丢弃任何一个任务,就选用这个策略。
    ③DiscardPolicy:表示拒绝处理新任务但不抛异常。如果允许任务丢失,这是最好的策略方案。
    ④DiscardOldestPolicy:表示丢弃掉队列里最旧的未处理的任务,不抛异常。

  • ThreadFactory:用于设置创建线程的工厂。(可以在自定义的ThreadFactory内将所有线程都设置为守护线程,这样当主线程退出后,会销毁线程池。)

5.线程池中一般设置多少线程?线程池大小如何设置?

  • (在京东实习中接触的项目,对于线程池参数设置的是核心线程数为5,最大线程数设置的是10,队列大小为1000,因为cpu核数是4,按照经验应该设置为2倍的cpu核数+1,所以应该是9,我们于是设置为10了;队列大小之所以设为1000,是因为我们处理的批次是100,设为1000的话可以放十个批次,而我们的系统处理速度其实是很快的)
  • 对于CPU密集型任务(N+1):尽量使用较小的线程池,一般设置线程池大小为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程,只会增加上下文切换的次数,带来额外的开销。
  • 对于IO密集型任务:由于这类任务对CPU的使用率不高,所以可以设置较大点的线程池,让CPU在等待IO的时候去处理别的任务,充分利用CPU的资源。一般设置为 CPU核心数 乘以 CPU使用率 再乘以 线程等待时间与运行时间的比例加1 得到的值,也就是线程等待时间所占比例越高,就设置越多的线程;线程运行时间所占比例越高,就设置越少的线程。
      CPU核心数 x CPU使用率 x [(线程等待时间 / 线程运行时间) + 1]
  • (如果线程池设置的太大,大量线程可能会同时争取CPU资源,导致产生大量的上下文切换,而且管理这么多线程,开销也会增大。如果设置太小,同一时间有大量任务需要处理时,就会导致任务在队列中等待执行,大量任务堆积会导致 OOM)

6. 什么是上下文切换?

  当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

7.任务队列满了以后再来一个任务如何处理?

  判断线程池运行的线程数量是否达到了最大线程数量。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则执行拒绝策略。
  

8.如何向线程池提交任务?(execute 方法和 submit 方法的区别)

  • execute 方法没有返回值,所以无法判断任务是否被线程池执行成功。
  • submit 方法有返回值,返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功。
阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池
### Java线程池常见的面试问题及解答 #### 1. **什么是Java线程池?** Java线程池是一种管理和重用多个可执行任务的工作线程机制。它通过预先创建一组工作线程来减少频繁创建和销毁线程带来的开销,从而提高程序性能[^2]。 #### 2. **Java线程池的核心组件有哪些?** Java线程池的主要核心组件包括以下几个部分: - **Core Pool Size**: 线程池中保持的最小线程数,即使这些线程处于空闲状态。 - **Maximum Pool Size**: 线程池中允许的最大线程数。 - **Work Queue**: 存放等待执行的任务队列。 - **Keep-Alive Time**: 当前活动线程数超过核心线程数时,多余的空闲线程在终止前所能存活的时间。 - **Rejected Execution Handler**: 当提交新任务而线程池时所采取的策略。 #### 3. **常用的几种线程池及其特点是什么?** 以下是`Executors`类提供的几种常用线程池及其特性: - `newFixedThreadPool(int nThreads)`:创建固定大小的线程池,适用于负载较重的场景。 - `newSingleThreadExecutor()`:创建单线程化的线程池,适合串行化处理任务。 - `newCachedThreadPool()`:根据需要创建新的线程,但在先前构建的线程可用时将重用它们,适用于执行大量短生命周期的任务。 - `newScheduledThreadPool(int corePoolSize)`:支持定时及周期性任务调度的线程池。 需要注意的是,在使用以上方法时可能面临内存溢出的风险,具体原因在于某些实现未设置合理的边界条件。 #### 4. **如何避免线程池引发的OutOfMemoryError (OOM)?** 为了避免因不当配置而导致的内存溢出错误,可以考虑以下几点建议: - 对于`newFixedThreadPool`和`newSingleThreadExecutor`,应明确设定请求队列容量而非默认无界队列。 - 针对`newCachedThreadPool`与`newScheduledThreadPool`,需严格控制最大线程数目以及合理规划任务超时时间,防止无限增长造成资源耗尽。 #### 5. **Spring框架下是如何管理线程池的?** 在Spring环境中可以通过定义Bean的方式自定义线程池参数,并将其注入到业务逻辑层中统一调配;此外还可以借助TaskExecutor接口简化多线程操作流程,提升开发效率的同时也便于维护和扩展。 ```xml <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="5"/> <property name="maxPoolSize" value="10"/> <property name="queueCapacity" value="25"/> </bean> ``` #### 6. **反射技术在线程池中有何应用价值?** 虽然反射主要用于运行期动态加载类并访问其成员变量或调用方法等功能上,但它也可以用来深入理解线程池内部结构——例如利用Class对象获取Constructor、Field或者Method实例进而分析不同类型的线程池究竟是怎样工作的[^1]。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值