滴滴一面:线程池任务,如何设置优先级?

说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如滴滴、极兔、有赞、希音、百度、网易的面试资格,遇到很多很重要的面试题:

  • 如何设计线程池?
  • 请手写一个简单线程池?

就在昨天, 一个小伙伴面试滴滴, 遇到一个与线程池底层原理有关的连环炮, 没有回答好,导致面试挂了。

小伙伴遇到的滴滴的面试真题,也就这个与线程池底层原理有关的连环炮,用小伙的原话来说吧。

小伙伴的原话如下:

恩哥,最近滴滴一面遇到一个问题,问的是

  • 线程池底部是怎么进行线程调度的,
  • 线程如何进行抢占和优先级设置,
  • 对于有优先级要求的场景下,怎么设置线程池。网上一直没找到答案

尼恩提示:线程池的知识,既是面试的核心知识,又是开发的核心知识。 所以,这里尼恩给大家做一下系统化、体系化的线程池梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”

也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V110版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】取

文章目录

    • 说在前面
    • 滴滴的面试真题分析
    • 线程池的基本原理
    • 对于有优先级要求的场景下,怎么设置线程池?
    • 优先级任务线程池的设计与实操
      • 使用 PriorityBlockingQueue 作为 线程池的任务队列
      • 提交的任务 具备 排序能力
    • 对自定义的优先级线程池,进行测试
    • PriorityBlockingQueue 队列的问题
    • 说说 PriorityQueue 的堆结构
      • PriorityQueue 是 Java 提供的堆实现
      • 来到算法的基础知识,什么是堆?
    • 在堆中添加元素
    • 在堆中删除元素
    • 说在最后
    • 推荐阅读

滴滴的面试真题分析

第一问:线程池底部是怎么进行线程调度的?

这个是一个基础题,具体的答案,请参考尼恩的 《Java 高并发核心编程 卷1 加强版》

第二问:线程如何进行抢占和优先级设置?

这个需要大家了解一下线程池的运作原理, 最好是自己手写一个简单的线程池,加深印象。

如何手写一个线程池呢? 请参考尼恩 架构团队的文章

网易一面:如何设计线程池?请手写一个简单线程池?

第三问:对于有优先级要求的场景下,怎么设置线程池?

这个,使用本文给大家作答。

线程池的基本原理

一般而言,大家都使用线程池并发执行、并发调度任务,通过池化的架构去节省线程创建和销毁带来的性能损耗。

默认情况下,有了线程池之后,大家都通过提交任务的方式, 提交任务到线程池,由线程池去调度。

提交到线程池的任务,按照线程池的调度规则进行调度。线程池的调度规则,大致如下:

注意:请点击图像以查看清晰的视图!

如何线程池的核心线程都很忙,任务就需要排队了,进入线程池的内部工作队列,大致如下图所示:

注意:请点击图像以查看清晰的视图!

工作线程执行完手上的任务后,会在一个无限循环中,反复从内部工作队列(如LinkedBlockingQueue )获取任务来执行。

对于有优先级要求的场景下,怎么设置线程池?

普通的线程池, 任务之间是没有优先级特权的, 可以理解为 先进先出 的公平调度模式。

有的的时候, 任务之间是有 优先级特权的, 不是按照 先进先出 调度, 而是需要按照 优先级进行调度。

所以,如果不同的任务之间,存在一些优先级的变化,咋整呢?

办法很简单,就是替换掉 线程池里边的工作队列,使用 优先级的无界阻塞队列 ,去管理 异步任务。

首先,来看看几种典型的工作队列

  • ArrayBlockingQueue:使用数组实现的有界阻塞队列,特性先进先出
  • LinkedBlockingQueue:使用链表实现的阻塞队列,特性先进先出,可以设置其容量,默认为Interger.MAX_VALUE,特性先进先出
  • PriorityBlockingQueue:使用平衡二叉树堆,实现的具有优先级的无界阻塞队列
  • DelayQueue:无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最块要过期的元素。
  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

使用 优先级的无界阻塞队列 PriorityBlockingQueue 替代 没有优先级的队列 ArrayBlockingQueue 或者 LinkedBlockingQueue。

额外提一嘴, 如果是普通的任务,没有优先级的话, 一般情况,建议大家参考 rocketmq的源码, 使用 有界的 LinkedBlockingQueue 作为 任务队列。

rocketmq的源码, 用到大量的线程池,具体如下图:

这些任务队列,用的都是有界的 LinkedBlockingQueue ,具体如下图:

如果任务有优先级, 就需要引入任务队列并进行管理了。

这时候,就需要 使用 优先级的无界阻塞队列 PriorityBlockingQueue ,下面是一个例子。

优先级任务线程池的设计与实操

实现一个 优先级任务线程池,有2个关键点:

  • 使用PriorityBlockingQueue 作为 线程池的任务队列。
  • 提交的任务 具备 排序能力。

使用 PriorityBlockingQueue 作为 线程池的任务队列

还是基于ThreadPoolExecutor 进行线程池的构造, 我们知道, ThreadPoolExecutor的构造函数有一个workQueue参数,这里可以传入 PriorityBlockingQueue 优先级队列。

在 buildWorkQueue() 方法里边,构造一个 PriorityBlockingQueue<E>,它的构造函数可以传入一个比较器Comparator,能够满足要求。

这里主要有两点:

  • 替换线程池默认的阻塞队列为 PriorityBlockingQueue,响应的传入的线程类需要实现 Comparable<T> 才能进行比较。
  • PriorityBlockingQueue 的数据结构决定了,优先级相同的任务无法保证 FIFO,需要自己控制顺序。

提交的任务 具备 排序能力

ThreadPoolExecutor的submit、invokeXxx、execute方法入参都是Runnable、Callable,均不具备可排序的属性。

我们可以弄一个实现类 PriorityTask,加一些额外的属性,让它们具备排序能力。

对自定义的优先级线程池,进行测试

提交三种不同优先级的任务,进行测试

  • 优先级高的后面提交
  • 优先级低的前面提交

优先级高的前面执行

优先级低的后面执行

PriorityBlockingQueue 队列的问题

主要是,PriorityBlockingQueue是无界的,它的offer方法永远返回true。

这样,会带来两个问题:

第一,OOM风险;

第二,最大线程数 失效

第三,拒绝策略 失效

怎么解决呢?

方法一:可以继承PriorityBlockingQueue , 重写一下这个类的offer方法,如果元素超过指定数量直接返回false,否则调用原来逻辑。

方式二:扩展线程池的submit、invokeXxx、execute方法,在里边进行 任务数量的 统计、检查、限制。

优先建议大家使用 方式一。

说说 PriorityQueue 的堆结构

面试进行到这里,很容易出现 PriorityQueue的堆结构的问题

因为, PriorityBlockingQueue 是 PriorityQueue 的阻塞版本

PriorityQueue 是 Java 提供的堆实现

PriorityQueue在默认情况下是一个最小堆,如果使用最大堆调用构造函数就需要传入 Comparator 改变比较排序的规则。

// 构造小顶堆
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>((o1, o2) -> o1 - o2);// 构造大顶堆
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>((o1, o2) -> o2 - o1);

PriorityQueue 实现了接口 Queue,它常用的函数如表所示

这就是为啥堆被称为优先级队列的原因

操作抛异常不抛异常
插入新的元素add(e)offer(e)
删除堆顶元素removepoll
返回堆顶元素elementpeek

虽然Java中的PriorityQueue实现了Queue接口,但它并不是一个队列,也不是按照“先入先出”的顺序删除元素的。

PriorityQueue的删除顺序与元素添加的顺序无关。PriorityQueue是 按照最小堆 的 次序,进行元素操作的。

所以,PriorityQueue是一个堆,每次调用函数remove或poll都将删除位于堆顶的元素。

同理,PriorityQueue的函数element和peek都返回位于堆顶的元素,即根据堆的类型返回值最大或最小的元素,这与元素添加的顺序无关。

来到算法的基础知识,什么是堆?

堆(也称为优先级队列)是一种特殊的树形数据结构。

根据根节点的值与子节点的值的大小关系,堆又分为最大堆和最小堆。

  • 在最大堆中,每个节点的值总是大于或等于其任意子节点的值,因此最大堆的根节点就是整个堆的最大值
  • 在最小堆中,每个节点的值总是小于或等于其任意子节点的值,因此最小堆的根节点就是整个堆的最小值

例如,图(a)所示是一个最大堆,图(b)所示是一个最小堆。

堆通常用完全二叉树实现。在完全二叉树中,除最低层之外,其他层都被节点填满,最低层尽可能从左到右插入节点。上图中的两个堆都是完全二叉树。

完全二叉树又可以用数组实现,因此堆也可以用数组实现。如果从堆的根节点开始从上到下按层遍历,并且每层从左到右将每个节点按照 0、1、2 等的顺序编号,将编号为 0 的节点放入数组中下标为 0 的位置,编号为1的节点放入数组中下标为1的位置,以此类推就可以将堆的所有节点都添加到数组中。上图(a)中的堆可以用数组表示成下图(a)所示的形式,而上图(b)中的堆可以用数组表示成下图(b)所示的形式。

如果数组中的一个元素的下标为 i,那么它在堆中对应节点的父节点在数组中的下标为 (i - 1) / 2,而它的左右子节点在数组中的下标分别为 2 * i + 12 * i + 2

在堆中添加元素

为了在最大堆中添加新的节点,有以下三个步骤:

  1. 先从上到下、从左到右找出第 1 个空缺的位置,并将新节点添加到该空缺位置
  2. 如果新节点的值比它的父节点的值大,那么交换它和它的父节点
  3. 重复这个交换过程,直到新节点的值小于或等于它的父节点,或者它已经到达堆的顶部位置。

在最小堆中添加新节点的过程与此类似,唯一的不同是要确保新节点的值要大于或等于它的父节点。

所以,堆的添加操作是一个自下而上的操作。

举个例子,如果上图(a)的最大堆中添加一个新的元素 95:

  • 由于节点 60 的右子节点是第1个空缺的位置,因此创建一个新的节点95并使之成为节点60的右子节点
  • 此时新节点95的值大于它的父节点60的值,这违背了最大堆的定义,于是交换它和它的父节点
  • 由于新节点95的值仍然大于它的父节点90的值,因此再交换新节点95和它的父节点90。此时堆已经满足最大堆的定义。

整体过程如下图:

堆的插入操作可能需要交换节点,以便把节点放到合适的位置,交换的次数最多为二叉树的深度,因此如果堆中有 n 个节点,那么它的插入操作的时间复杂度是 O(logn)

在堆中删除元素

通常只删除位于堆顶部的元素

以删除最大堆的顶部节点为例:

  1. 将堆最低层最右边的节点移到堆的顶部
  2. 如果此时它的左子节点或右子节点的值大于它,那么它和左右子节点中值较大的节点交换
  3. 如果交换之后节点的值仍然小于它的子节点的值,则再次交换,直到该节点的值大于或等于它的左右子节点的值,或者到达最低层为止

删除最小堆的顶部节点的过程与此类似,唯一的不同是要确保节点的值要小于它的左右子节点的值。

所以,堆的删除操作是一个自上而下的操作。

举个例子,删除上图(a)中最大堆的顶部元素之后:

  1. 将位于最低层最右边的节点60移到最大堆的顶部,如下图(c)所示
  2. 此时节点60比它的左子节点80和右子节点90的值都小,因此将它和值较大的右子节点90交换,交换之后的堆如图(d)所示
  3. 此时节点60大于它的左子节点30,满足最大堆的定义

堆的删除操作可能需要交换节点,以便把节点放到合适的位置,交换的次数最多为二叉树的深度,因此如果堆中有 n 个节点,那么它的删除操作的时间复杂度是 O(logn)

说在最后

线程池面试题,是非常常见的面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,并且在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

推荐阅读

《百亿级访问量,如何做缓存架构设计》

《多级缓存 架构设计》

《消息推送 架构设计》

《阿里2面:你们部署多少节点?1000W并发,当如何部署?》

《美团2面:5个9高可用99.999%,如何实现?》

《网易一面:单节点2000Wtps,Kafka怎么做的?》

《字节一面:事务补偿和事务重试,关系是什么?》

《网易一面:25Wqps高吞吐写Mysql,100W数据4秒写完,如何实现?》

《亿级短视频,如何架构?》

《炸裂,靠“吹牛”过京东一面,月薪40K》

《太猛了,靠“吹牛”过顺丰一面,月薪30K》

《炸裂了…京东一面索命40问,过了就50W+》

《问麻了…阿里一面索命27问,过了就60W+》

《百度狂问3小时,大厂offer到手,小伙真狠!》

《饿了么太狠:面个高级Java,抖这多硬活、狠活》

《字节狂问一小时,小伙offer到手,太狠了!》

《收个滴滴Offer:从小伙三面经历,看看需要学点啥?》

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/139818.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

认识面向对象-PHP8知识详解

面向对象编程&#xff0c;也叫面向对象程序设计&#xff0c;是在面向过程程序设计的基础上发展而来的&#xff0c;它比面向过程编程具有更强的灵活性和扩展性。 它用类、对象、关系、属性等一系列东西来提高编程的效率&#xff0c;其主要的特性是可封装性、可继承性和多态性。…

NIO简单介绍

一、什么是NIO 1、Java NIO全称java non-blocking IO&#xff0c; 是指JDK提供的新API。从JDK1.4开始&#xff0c;Java提供了一系列改进的输入/输出的新特性&#xff0c;被统称为NIO(即New IO)&#xff0c;是同步非阻塞的 2、NIO有三大核心部分: Channel(通道)&#xff0c; Buf…

leetcode1516.移动N叉树的子树

题目 给定一棵没有重复值的 N 叉树的根节点 root ,以及其中的两个节点 p 和 q。 移动节点 p 及其子树,使节点 p 成为节点 q 的直接子节点。 如果 p 已经是 q 的直接子节点,则请勿改动任何节点。 节点 p 必须是节点 q 的子节点列表的最后一项。 返回改动后的树的根节点。 节点…

WebGL 从0到1绘制一个立方体

目录 前言 组成立方体的面、三角形、顶点坐标和顶点颜色 通过顶点索引绘制物体 gl.drawElements(mode, count, type, offset) 函数规范 示例程序 彩色立方体&#xff08;HelloCube.js&#xff09; 代码详解 向缓冲区中写入顶点的坐标、颜色与索引 gl.ELEMENT_ARRAY_B…

CorelDraw是什么软件?好用吗

很多人都听过CorelDraw的名字&#xff0c;但不知道CorelDraw是什么样的软件。下面就让小编为大家详细介绍一下。 coreldraw是什么软件 CorelDraw是一款专业的图形设计软件。它的主要功能包括矢量图形和位图的编辑。用户可以利用其矢量图形编辑能力,设计各种图标、Logo等精细图…

java框架-Spring-事务

配置 配置事务管理器方法&#xff1a; Beanpublic PlatformTransactionManager platformTransactionManager(){return new DataSourceTransactionManager();}原理

短信登录功能如何实现?

简介&#xff1a; 在日常生活中我们登录/注册某些网站/APP是通常可以选择 密码登录和手机号登录。 为什么手机号发送后会有验证码返回呢&#xff1f; 网站如何识别我的验证码是否正确&#xff1f; 如果我的个人网站也想要实现短信登录功能&#xff0c;具体该如何实现&#xff1…

Webpack监视文件修改,自动重新打包文件

方法一&#xff1a;使用watch监视文件变化 在终端中输入以下指令&#xff1a; npx webpack --watch 我们使用这种方法监听文件变化时只会监听我们计算机本地的文件变化&#xff0c;在开发场景中我们的项目是要部署到服务器中的&#xff0c;因此这种方式并不推荐。 方法二&…

8款常见的自动化测试开源框架

在如今开源的时代&#xff0c;我们就不要再闭门造车了&#xff0c;热烈的拥抱开源吧&#xff01;本文针对性能测试、Web UI 测试、API 测试、数据库测试、接口测试、单元测试等方面&#xff0c;为大家整理了github或码云上优秀的自动化测试开源项目&#xff0c;希望能给大家带来…

Python_it_heima

P63 list P68 元组 注意&#xff1a;元组内部嵌套的list包含的内容可以修改&#xff0c;但list本身不能修改。 P69 字符串 P71 数据容器&#xff08;序列&#xff09;的切片 P73 集合 P75 字典 字典的常用操作 字典课后练习 P78 类数据容器的总结对比 P79 数据容器的通用操作 不…

useCallBack

React.memo 保证了只有props发生变化时&#xff0c;该组件才会重新渲染 &#xff08;当然组件内部的state 和 context 变化也会导致组件重新渲染&#xff09;&#xff0c;但咱们只要将咱们的子组件包裹&#xff0c;便可以保证Child组件在props不变的情况下&#xff0c;不会重新…

万字解析30张图带你领略glibc内存管理精髓

最近在逛知乎的时候&#xff0c;看到篇帖子&#xff0c;如下&#xff1a; 看了下面所有的回答&#xff0c;要么是没有回答到点上&#xff0c;要么是回答不够深入&#xff0c;所以&#xff0c;借助本文&#xff0c;深入讲解C/C内存管理。 1 写在前面 源码分析本身就很枯燥乏味…

007 数据结构_堆——“C”

前言 本文将会向您介绍关于堆Heap的实现 具体步骤 tips&#xff1a;本文具体步骤的顺序并不是源代码的顺序 typedef int HPDataType; typedef struct Heap {HPDataType* _a;int _size;int _capacity; }Heap;初始化 void HeapCreate(Heap* hp, HPDataType* a, int n) {hp-&…

代码随想录算法训练营 动态规划part08

一、单词拆分 139. 单词拆分 - 力扣&#xff08;LeetCode&#xff09; 将字符串 s 长度记为 n&#xff0c;wordDict 长度记为 m。为了方便&#xff0c;我们调整字符串 s 以及将要用到的动规数组的下标从 1 开始。 定义 f[i] 为考虑前 i 个字符&#xff0c;能否使用 wordDict 拼…

linux进程杀不死

项目场景&#xff1a; 虚拟机 问题描述 linux进程杀不死 无反应 原因分析&#xff1a; 进程僵死zombie 解决方案&#xff1a; 进proc或者find命令找到进程所在地址 cat status查看进程杀死子进程

多输入多输出 | MATLAB实现CNN-BiGRU卷积双向门控循环单元多输入多输出

多输入多输出 | MATLAB实现CNN-BiGRU卷积双向门控循环单元多输入多输出 目录 多输入多输出 | MATLAB实现CNN-BiGRU卷积双向门控循环单元多输入多输出预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 MATLAB实现CNN-BiGRU卷积双向门控循环单元多输入多输出&#xf…

Otter改造 增加springboot模块和HTTP调用功能

环境搭建 & 打包 环境搭建&#xff1a; 进入 $otter_home/lib 目录执行&#xff1a;bash install.sh 打包&#xff1a; 进入$otter_home目录执行&#xff1a;mvn clean install -Dmaven.test.skip -Denvrelease发布包位置&#xff1a;$otter_home/target 项目背景 阿里…

「大数据-0」虚拟机VMware安装、配置、使用、创建大数据集群教程

目录 一、下载VMware Wworkstation Pro 16 二、安装VMware Wworkstation Pro 16 三、检查与设置VMware的网卡 1. 检查 2. 设置VMware网段 四、在VMware上安装Linux虚拟机 五、对安装好的虚拟机进行设置 1. 打开设置 2. 设置中文 3. 修改字体大小 4. 修改终端字体大小 5. 关闭虚…

十、性能测试之数据库测试

性能测试之数据库测试 一、 数据库分类二、 mysql安装及密码的修改1、安装&#xff1a;数据库的版本 mysql5.7版方法1&#xff1a;直接安装方法2&#xff1a;使用rpm包安装方法3&#xff1a;docker方式安装 2、修改数据库的密码3、创建库4、创建表 三、存储引擎1、InnoDB特点 2…

进入数据结构的世界

数据结构和算法的概述 一、什么是数据结构二、什么是算法三、如何去学习数据结构和算法四、算法的时间复杂度和空间复杂度4.1 算法效率4.2 大O的渐进表示法4.3 时间复杂度4.4 空间复杂度4.5 常见复杂度对比 一、什么是数据结构 数据结构是计算机存储、组织数据的方式。&#x…