5年面试官划重点-executor线程池体系

231 阅读11分钟

面试难度:★★★★★

考察概率:★★★★★

#莫等闲,白了少年头#

本人从毕业开始一直在一线互联网大厂工作,现任技术TL,出版过《深入理解Java并发》一书,折腾过技术开源项目,并长期作为面试官参与面试,深谙双方的诉求与技术沟通。如今归零心态,再出发。#莫等闲,白了少年头#

技术交流+v:xxxyxsyy1234(和笔者一起努力,每日打卡) 2000+以面试官视角总结的考点,可与我共同打卡学习

公众号.jpg

面试官视角

说到并发,线程池是必不可少的一个话题,自然而言也是一个高频的考点。对候选人而言,如果能知道线程池背后的源码逻辑是一个加分项,另外对“池化”技术的解决方案有着自己的思考,一定会脱颖而出的,在技术领域一定会经常听到各种xxx"池"的,这种其实是一种很通用也很巧妙的解决方案。对于资深的开发来说,在业务建模上也会采用这种方案,如果能结合自己的业务项目,去表达自己的理解,一定会给面试官带来惊喜的!

面试题

1、为什么会使用线程池技术?

2、简述Java线程池的各个参数的意义。

3、简述可周期执行的任务 ScheduledFutureTask 的实现原理?

4、简述FutureTask的基本应用?

5、简述FutureTask和Future的关系?

回答要点

1.为什么会使用线程池技术?

线程池技术的使用有以下几个主要原因:

  1. 降低线程创建和销毁的开销:线程的创建和销毁是一项开销较大的操作。如果在每次需要执行任务时都创建一个新线程,会频繁地进行线程的创建和销毁,导致额外的开销。而线程池在初始化时会创建一定数量的线程,并将它们保存在池中,可以重复使用这些线程来执行多个任务,避免了频繁的线程创建和销毁开销,提高了系统的性能。
  2. 提高系统的并发性能:线程池可以限制系统中同时运行的线程数量,防止过多的线程导致系统资源的过度占用和竞争,从而提高系统的并发性能。通过合理调整线程池的大小,可以根据系统的资源情况和任务负载来控制并发度,平衡系统的吞吐量和资源消耗。
  3. 提供任务队列和调度功能:线程池通常配备任务队列,用于存储等待执行的任务。当线程池中的线程空闲时,可以从任务队列中获取新的任务进行执行,充分利用系统资源。同时,线程池可以根据需求和策略对任务进行调度,例如按照优先级、提交顺序或其他策略来执行任务。
  4. 控制线程的生命周期和资源消耗:线程池提供了对线程的生命周期和资源消耗的管理。可以通过配置线程池的参数来限制线程的最大数量、超时时间、线程的回收和重启等,从而避免线程资源的过度消耗和系统的不稳定。

2.简述Java线程池的各个参数的意义。

创建线程池时,可以通过设置不同的参数来配置线程池的行为和性能。以下是创建线程池时常用参数的意义:

  1. 核心线程数(corePoolSize):核心线程数指线程池中能够同时执行任务的最小线程数量。在线程池的生命周期内,核心线程始终存在,即使它们是空闲的。当有新任务提交时,线程池会优先创建核心线程来执行任务;
  2. 最大线程数(maximumPoolSize):最大线程数指线程池中能够同时执行任务的最大线程数量。当任务数量超过核心线程数,并且任务队列已满时,线程池会创建新的线程来执行任务,直到达到最大线程数;
  3. 线程存活时间(keepAliveTime):线程存活时间指非核心线程空闲时的存活时间。当线程池中的线程数大于核心线程数时,空闲时间超过线程存活时间的线程会被销毁,从而控制线程池中线程的数量。
  4. 时间单位(unit):用于指定线程存活时间的单位,例如秒、毫秒、分钟等。
  5. 任务队列(workQueue):任务队列用于存储等待执行的任务。线程池根据任务队列中的任务来分配线程执行。常见的任务队列有有界队列(如ArrayBlockingQueue)和无界队列(如LinkedBlockingQueue)。任务队列的选择会影响线程池的策略和行为。
  6. 线程工厂(threadFactory):线程工厂用于创建新的线程对象。可以自定义线程工厂来命名线程、设置线程的优先级等。
  7. 拒绝策略(handler):当线程池和任务队列都满了,无法继续接受新任务时,拒绝策略定义了线程池如何处理这种情况。常见的拒绝策略有抛出异常、丢弃任务、丢弃最早的任务和调用者运行等。

通过调整这些参数,可以根据具体的需求和系统情况来优化线程池的性能和行为。例如,核心线程数和最大线程数的选择可以根据任务的类型和并发负载进行调整,线程存活时间可以根据任务的响应时间和线程创建销毁的开销进行调整,任务队列的选择可以根据任务的特性和系统资源的情况进行选择,拒绝策略可以根据业务需求和系统的稳定性进行选择。

3.简述可周期执行的任务 ScheduledFutureTask 的实现原理。

ScheduledFutureTask是Java中用于表示可周期执行的任务的类。它是ScheduledExecutorService接口中scheduleAtFixedRate()和scheduleWithFixedDelay()方法的返回结果,用于表示延迟执行并周期重复执行的任务。

ScheduledFutureTask的实现原理如下:

  1. ScheduledFutureTask继承自FutureTask类,是其子类。FutureTask是一个可取消的异步计算任务,可以包装Callable或Runnable对象,并提供了异步执行和获取结果的功能。
  2. ScheduledFutureTask内部包含了一个Runnable或Callable对象,表示需要执行的任务。
  3. ScheduledFutureTask还维护了一个ScheduledFutureTask.DelayedTask对象,用于表示任务的调度信息。DelayedTask对象包含了任务的下次执行时间、间隔时间、任务的优先级等信息。
  4. 在ScheduledExecutorService的scheduleAtFixedRate()或scheduleWithFixedDelay()方法中,会创建一个ScheduledFutureTask对象,并将任务和调度信息封装到该对象中。
  5. 当调度时间到达时,ScheduledExecutorService会将ScheduledFutureTask对象放入执行队列中。
  6. 在ScheduledFutureTask中,重写了FutureTask的run()方法。在run()方法中,会执行任务的逻辑,并根据任务类型(是否周期重复执行)和调度信息来计算下次执行时间。
  7. 如果任务是周期重复执行的,计算下次执行时间的方式有两种:
    • scheduleAtFixedRate():根据任务的下次执行时间和任务的间隔时间计算下次执行时间。无论上次任务是否执行完成,都会按照间隔时间进行调度。
    • scheduleWithFixedDelay():根据任务的上次执行完成时间和任务的间隔时间计算下次执行时间。只有上次任务执行完成后才会按照间隔时间进行调度。
  1. ScheduledFutureTask还提供了一些其他方法,例如cancel()用于取消任务的执行,getDelay()用于获取任务的延迟时间等。

通过ScheduledFutureTask的实现,可以方便地实现可周期执行的任务,并根据调度信息自动计算下次执行时间。它在ScheduledExecutorService中起到了关键的作用,实现了定时任务的调度和执行功能。

4.简述FutureTask的基本应用。

FutureTask是Java中的一个类,用于表示一个可取消的异步计算任务,并提供了获取任务执行结果的方法。它的基本应用如下:

  1. 异步任务的执行:FutureTask可以封装一个Callable或Runnable对象,表示一个异步的计算任务。通过调用FutureTask的构造方法并传入相应的任务对象,可以创建一个FutureTask实例。
  2. 提交任务并获取结果:通过将FutureTask提交给ExecutorService或线程池进行执行,可以异步地执行任务。通过调用FutureTask的get()方法,可以获取任务执行的结果。get()方法会阻塞,直到任务执行完成并返回结果,或者抛出异常。
  3. 取消任务:FutureTask提供了cancel()方法用于取消任务的执行。调用cancel()方法后,如果任务尚未开始执行,则会将任务标记为已取消;如果任务已经在执行中,则会尝试中断任务的执行。通过调用isCancelled()方法可以检查任务是否被取消。
  4. 判断任务是否完成:通过调用isDone()方法可以判断任务是否执行完成,无论是正常完成、取消还是异常完成。
  5. 异步任务的依赖关系:FutureTask支持异步任务之间的依赖关系。一个任务可以等待另一个任务的完成,并根据前一个任务的结果执行后续操作。通过创建多个FutureTask实例并设置它们的依赖关系,可以实现异步任务的串行执行或并行执行。
  6. 错误处理:FutureTask可以捕获异步任务中发生的异常,并通过get()方法返回一个异常结果。通过处理异常结果,可以进行错误处理和异常恢复。
  7. 进度监控:FutureTask提供了一些方法,如isDone()和isCancelled(),可以用于监控任务的执行进度和状态。可以根据任务的执行情况来进行相应的处理,例如显示进度条或取消未完成的任务。

总而言之,FutureTask是一个非常有用的类,可以帮助我们方便地处理异步任务的执行和结果获取,支持任务的取消、依赖关系、错误处理和进度监控等功能。它在多线程和并发编程中广泛应用,提高了代码的可读性和可维护性。

5.简述FutureTask和Future的关系。

FutureTask是实现了Future接口的具体实现类。Future是一个接口,定义了对异步任务的结果进行操作的方法,如获取任务执行结果、取消任务等。

FutureTask可以看作是Future的一个具体实现,它提供了一些额外的方法来管理和控制异步任务的执行。通过FutureTask,我们可以将一个Callable或Runnable对象包装成一个异步任务,并通过调用其get()方法来获取任务执行的结果。

在Java中,FutureTask通常与ExecutorService结合使用,用于提交任务并获取任务执行结果。ExecutorService接口提供了submit()方法,可以接受一个FutureTask作为参数,并返回一个Future对象。通过这个Future对象,我们可以操作任务的执行结果。

FutureTask和Future之间的关系如下:

  1. FutureTask实现了Future接口,因此FutureTask可以被当作Future来使用。
  2. FutureTask提供了Future接口中定义的方法,如get()、cancel()等,用于获取任务执行结果和取消任务。
  3. FutureTask扩展了Future接口,提供了额外的方法,如run()、isDone()、isCancelled()等,用于控制和管理异步任务的执行。
  4. FutureTask可以通过submit()方法提交给ExecutorService执行,并返回一个Future对象,用于获取任务执行的结果。

总结来说,FutureTask是Future接口的具体实现,提供了更多功能和方法来管理和控制异步任务的执行。FutureTask可以用于提交任务、获取任务执行结果、取消任务和监控任务的执行状态。通过FutureTask和Future的结合使用,我们可以更加灵活地处理异步任务的执行和结果获取。

代码考核

对线程池的考察,不会涉及到手撕代码的环节,这块的面试考察点也多是结合业务项目来展开讲的,如果候选人在简历或者业务项目中聊到这块,就需要在日常中多加思考。

知识点详情

这部分可以参考本人的书籍《深入理解Java并发》,或者本人博客

1、juejin.cn/post/684490…

2、juejin.cn/spost/68449…