百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

互联网大厂面试系列-面试中被问到Java线程池核心参数有哪些?

wxin55 2024-11-07 13:13 10 浏览 0 评论

无论是我们的日常开发中,还是在面试中,多线程都是我们避免不了的一个话题,而问到多线程相关问题,就不得不提及线程优化,而提到线程优化就不得不提到线程池相关的内容。下面我们就来聊一聊关于Java线程池相关的内容。

线程池是什么?

首先来讲,线程池是一种使用了池化技术思想来管理线程的工具,被用来管理多线程相关的内容。在这之前我们了解最多的池化思想使用的场景就是数据库连接池,说白了数据库连接池其实就可以看做一个线程池。

线程的创建、调度、销毁都是很占用资源的操作。而多线程的使用就更加去浪费这一部分资源,由于多线程的加入,会极大的影响计算机整体的性能表现。这个时候就出现了线程池化技术,通过线程池技术可以同时维护多个线程的资源,从而达到多线程资源之间的相互高效利用。这种做法一方面是避免了任务处理的时候对于创建线程、销毁线程所带来的开销,另一方面也可以控制线程膨胀带来的CPU过度调度的问题,可以保证内核的充分利用。

线程池用来解决什么问题?

根据上面的介绍线程池所解决的核心问题就是对于线程资源的管理问题。现在的系统设计都离不开并发场景的考虑。但是在并发场景中系统却不能确定在什么时候会有多少线程需要去执行,而这种不确定性所带的问题有如下一些。

  • 多线程的频繁创建、销毁,任务执行,线程上下文的切换等都会带来很大的内存开销,影响应用的正常使用。
  • 如果出现了大量的线程无法释放,线程指数增长,就会导致资源无法申请,因为我们知道线程的创建也是需要空间的,如果创建的线程过多而没有正常释放的话就会导致内存溢出等问题,导致应用无法正常使用。
  • 系统资源无法得到高效的利用,有时候有些耗时操作会占用CPU大量的时间导致其他线程由于各种资源竞争问题而无法得到有效执行,从而导致系统运行不稳定,功能时好时坏。

为了解决这些问题,就有人提出了池化思想,顾名思义就是通过一个统一的管理池来对线程资源进行有效的管理。而池化思想比较经典的使用就是线程池、数据库连接池、常量池等等。

线程池的核心组件和核心类

说到线程池,主要是由如下的四个核心组件构成

  • 线程池管理器:由用户创建用来进行线程的管理
  • 工作线程:在线程池中用来执行具体任务的线程
  • 任务接口:用来定义工作线程需要调度和执行的任务策略,只有实现了对应的接口,任务才会被线程池所调度。
  • 任务队列:用来存放待处理任务的队列,新的待执行任务将会被不断的加入到该队列中,执行完成的任务将会从队列中被移除。

Java中的线程池是通过Executor框架来实现,其中包括了Executor、Executors、ExecutorService、ThreadPoolExecutor、Callable、Future、FutureTask等核心类实现。其类继承关系如下图所示

其中ThreadPoolExecutor实现的顶层接口就是Executor,而Executor所提供的设计思想就是将任务的提交与任务的执行进行了解耦操作,使用者可以不需要关心线程是如何被创建,如何进行任务调度,如何来进行线程的销毁等。而是只需要关心执行什么样的业务逻辑,通过继承Runnable接口来实现具体的业务逻辑并且提交到执行器Executor即可,剩下的操作则是由Executor来完成。

在ExecutorService中,还增加了一部分的能力。第一、就是对执行任务能力的扩展,补充了可以为一个或者一批异步任务生成一个Future的方法;第二、提供了线程池管控的方法,例如停止线程池执行等。

在AbstractExecutorService 中 则是将所有的线程池执行操作进行了串联,保证了在下层执行中只需要调用一个方法就可以将整个的线程池的执行流程串联起来。

而在ThreadPoolExecutor中则是实现了一些复杂的调用。一方面维护了自身运行的生命周期,另一方面就是对线程池的任务的生命周期进行管理了管理。其运行机制如下图所示。

ThreadPoolExecutor 详解

根据上面介绍ThreadPoolExecutor作为线程池的核心类,它有如下的一个构造方法。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

下面我们就来介绍一下构造方法中的一些核心参数。

  • corePoolSize :核心线程数,当任务提交到线程池的时候,如果线程池中线程的数量还没有达到corePoolSize的值,那么就会新创建一个线程来执行该任务,如果达到了就将任务添加到任务等待队列中。
  • maximumPoolSize:最大线程数:当任务队列满了之后,如果再有新的线程进入到线程池,这个时候就会判断如果新建一个线程是否会超过maximumPoolSize的值,如果会超过,那么就不会创建线程,然后去执行拒绝策略,如果不会超过,则会创建一个新的线程来执行该任务。
  • keepAliveTime:当线程池中的线程大于corePoolSize的时候,那么大于corePoolSize这个部分的线程如果没有对应的任务需要去处理,那么就表示这些线程进入到了一个空闲的状态,这个时候线程池为了节省资源是不会允许这些线程存在的,只会允许它们等待一段时间之后就进行销毁了,而这个指定的空闲等待时间就是keepAliveTime值。其单位就是由TimeUnit来确定。
  • unit:空闲线程等待时间单位,就是上面的这个TimeUnit,它是一个枚举类型,用来表示时间单位。
  • workQueue:任务队列,这个队列就是上面我们提到的用来存放待执行任务。该队列的类型是阻塞队列,常用的阻塞队列有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue等。
  • threadFactory:线程池工厂,用来创建线程。通常在实际项目中,为了便于后期排查问题,在创建线程时需要为线程赋予一定的名称,通过线程池工厂,可以方便的为每一个创建的线程设置具有业务含义的名称。
  • handler:拒绝策略。当任务队列已满,线程数量达到maximumPoolSize后,线程池就不会再接收新的任务了,这个时候就需要使用拒绝策略来决定最终是怎么处理这个任务。默认情况下使用AbortPolicy,表示无法处理新任务,直接抛出异常。在ThreadPoolExecutor类中定义了四个内部类,分别表示四种拒绝策略。我们也可以通过实现RejectExecutionHandler接口来实现自定义的拒绝策略。

任务队列介绍

  • ArrayBlockingQueue是一个基于数组实现的阻塞队列,元素按照先进先出(FIFO)的顺序入队、出队。因为底层实现是数组,数组在初始化时必须指定大小,因此ArrayBlockingQueue是有界队列。
  • LinkedBlockingQueue是一个基于链表实现的阻塞队列,元素按照先进先出(FIFO)的顺序入队、出队。因为顶层是链表,链表是基于节点之间的指针指向来维持前后关系的,如果不指链表的大小,它默认的大小是Integer.MAX_VALUE,即,这个数值太大了,因此通常称LinkedBlockingQueue是一个无界队列。当然如果在初始化的时候,就指定链表大小,那么它就是有界队列了。
  • SynchronousQueue是一个不存储元素的阻塞队列。每个插入操作必须得等到另一个线程调用了移除操作后,该线程才会返回,否则将一直阻塞。吞吐量通常要高于LinkedBlockingQueue。
  • PriorityBlockingQueue是一个将元素按照优先级排序的阻塞的阻塞队列,元素的优先级越高,将会越先出队列。这是一个无界队列。

线程池执行流程

如图所示,线程池的使用执行流程。其主要有三大核心执行策略。如下

  1. 首先判断线程池中的线程数是否大于核心线程数,如果没有超过核心线程数,那么就需要创建新的线程来执行任务,如果超过了核心线程数,那么就进入到队列检查
  2. 判断队列是否满了,如果没有满,就将执行任务添加到队列中进行等待,如果满了之后就需要检查最大线程数。
  3. 进入到最大线程数检查,如果线程池没有达到最大线程数,那么就创建一个新的线程来执行任务,如果已经达到了最大线程数那么就需要使用拒绝策略。

总结

这里最容易混淆的就是达到核心线程数之后的操作。很多人会认为如果当前线程池中的线程数达到了核心线程数之后,后续进入的线程就会直接创建新的线程,然后一直等到达到最大线程数的时候就才会往线程队列中添加待执行任务,一直到队列满了之后,执行拒绝策略,其实不然。根据上面的介绍当核心线程数达到之后,后续进入的线程是先进入到队列中,当队列满了之后才会开启新的线程执行,一直到最大线程之后,才会触发拒绝策略。这一点是需要大家注意的。到这里整个的线程池核心参数介绍就结束了。希望能够帮助到大家。关于线程池的其他面试相关问题,我们会在后续的分享中给大家介绍,希望大家多多关注。

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: