对线面试官-线程池(一)

2023-06-28 15:11:58 浏览数 (1)

面试官:你知道创建线程的方式有哪些吗?

派大星:主要有两种方式,

  • 一种是通过 Executors 创建,
  • 另一种是通过 ThreadPoolExecutorPools 创建。

面试官:很好。你能说一下这些创建方式的优缺点吗?

派大星:当然。但是我要先提醒一下,不建议使用 Executors 创建线程。

  • FixedThreadPoolSingleThreadPool 允许的请求队列长度为 Integer.MAX_VALUE,从而可能会堆积大量请求,造成 OOM;
  • CachedThreadPoolScheduledThreadPool 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。除此之外,Executors 创建的线程池无法自定义配置,不够灵活。建议使用 ThreadPoolExecutorPools创建方式进行自定义配置

面试官你能介绍一下 Executors 吗?

派大星:Executors 是一个工具类,提供了创建线程池的方法。它提供了 6 种创建线程池的方式,包括 newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool 等。

面试官那你能介绍一下 newFixedThreadPool 的使用方法吗?

派大星:当然可以。newFixedThreadPool 可以创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。我们可以通过以下代码来创建一个固定大小的线程池:

代码语言:javascript复制
ExecutorService threadPool = Executors.newFixedThreadPool(2);

面试官那你能介绍一下 Executors 提供的其他创建方式吗?

派大星:当然可以。除了 newFixedThreadPool,Executors 还提供了其他 5 种创建方式,分别是 newCachedThreadPoolnewSingleThreadExecutornewScheduledThreadPoolnewSingleThreadScheduledExecutornewWorkStealingPool

  • newCachedThreadPool 可以创建一个可以缓存的线程池,若线程数超过处理所需,则会缓存一段时间后回收。若线程数不够,则新建线程。
  • newSingleThreadExecutor 可以创建单个线程数的线程池,它可以保证先进先出的执行顺序。
  • newScheduledThreadPool 可以创建一个可以执行延迟任务的线程池。
  • newSingleThreadScheduledExecutor 可以创建一个单线程的可以执行延迟任务的线程池。
  • newWorkStealingPool 可以创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8 版本中才能使用。

面试官这些创建方式有什么优缺点呢

派大星:这些创建方式各有优缺点。例如

  • newCachedThreadPool 可以根据任务数量自动调整线程池的大小,但是如果任务数量过多,会导致线程数过多,从而导致系统资源不足。
  • 而 newFixedThreadPool 可以限制线程的数量,避免线程数过多,但是如果任务数量过多,会导致任务在队列中等待,从而导致响应时间变慢。

面试官那你建议我们使用哪种创建方式呢?

派大星:我建议使用 ThreadPoolExecutorPools 创建方式进行自定义配置。因为 Executors 创建方式的缺点是无法进行自定义配置,而 ThreadPoolExecutorPools 可以通过自定义配置来满足不同的需求。

面试官你知道线程池的参数有哪些吗?

派大星:一共有七个参数。

  • 第一个是核心线程数(corePoolSize),表示线程池中始终存在的线程数;
  • 第二个是最大线程数(maximumPoolSize),表示线程池中允许的最大线程数,当线程池中的任务队列满了之后可以创建的最大线程数;
  • 第三个是最大线程数可以存活的时间(maximumPoolSize),当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程;
  • 第四个是时间单位(unit),用于设定线程的存活时间,有七种可以选择,包括天、小时、分、秒、毫秒、微妙和纳秒;
  • 第五个是一个阻塞队列(unit),用来存储线程池等待执行的任务,包含七种类型
    • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
    • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
    • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
    • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
    • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
    • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
  • 第六个是线程工厂(threadFactory),主要用来创建线程,默认为正常优先级,非守护线程;
  • 第七个是拒绝策略(handler),用于拒绝处理任务时的策略,系统有四种可选。默认的策略为抛出异常。
    • AbortPolicy:拒绝并抛出异常
    • CallerRunsPolicy:使用当前调用的线程来执行任务
    • DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务
    • DiscardPolicy:忽略并抛弃当前任务

所以综上情况所述,我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避一些未知的风险。

0 人点赞