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

news2024/10/6 6:50:47

说在前面

在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.coloradmin.cn/o/1034784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

肖sir__mysql之综合题练习__013

数据库题&#xff08;10*5&#xff09; 下面是一个学生与课程的数据库&#xff0c;三个关系表为&#xff1a; 学生表S&#xff08;Sid&#xff0c;SNAME,AGE,SEX&#xff09; 成绩表SC&#xff08;Sid&#xff0c;Cid&#xff0c;GRADE&#xff09; 课程表C&#xff08;Cid&…

linux进程杀不死

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

【AI视野·今日CV 计算机视觉论文速览 第252期】Fri, 22 Sep 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 22 Sep 2023 Totally 90 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;SVGCustomization, 基于文本的矢量图生成定制(from 香港城市大学)。 website:https://intchous.github.io/SVGCustomization/ …

OVS-DPDK/虚拟化学习

用户/内核空间虚拟化 NFV和Middlebox的不同数据平面模型&#xff0c;具有不同的虚拟交换机选项、虚拟设备接口和虚拟化框架&#xff1a;(a)基于内核的vSwitch virtio-user/vhost-net和TUN/TAP VM&#xff1b;(b)基于内核的vSwitch virtio-user/vhost-net和TUN/TAP contain…

什么算泄露公司机密的行为(什么程度算公司泄密行为)

在当今的商业环境中&#xff0c;保护公司的核心竞争力和商业机密是至关重要的。然而&#xff0c;员工可能出于各种原因泄露这些信息&#xff0c;包括对竞争对手的追求、个人利益的驱动或者对工作的不满。在这种情况下&#xff0c;企业需要依赖专业的调查工具来揭示和证明员工的…

OpenGL之相机

OpenGL本身没有摄像机(Camera)的概念&#xff0c;但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机&#xff0c;产生一种我们在移动的感觉&#xff0c;而不是场景在移动。 本节我们将会讨论如何在OpenGL中配置一个摄像机&#xff0c;并且将会讨论FPS风格的…

Nginx使用指南

文章目录 前言一、源码编译1.1 编译1.2 第三方模块编译 二、配置文件2.1 配置语法2.2 location语法2.3 配置文件块2.4 全局变量 三、HTTP 服务器3.1 基本3.2 反向代理3.3 压缩3.4 负载均衡3.5 HTTPS 支持3.6 UrlRewrite3.7 防盗链配置3.8 跨域3.9 静态服务3.10 PC/手机端分离3.…

Web3 solidity编写fillorder填充订单函数 并梳理讲述逻辑

好 经过上文 Web3 solidity编写cancelorder取消订单函数 并梳理讲述逻辑 我们成功编写了 cancelorder 取消订单函数 其实 做了取消订单 填充订单 已经是非常简单的事了 我们还是先起来ganache 虚拟环境 这里 我们 模仿 orderCancel 在做一存储结构 //存储被填充订单 mapping…

追光者的梦

追光者的梦 鸿蒙中我茫然于世&#xff0c;你是钻入我心里的那束光 我所有的梦想都是和你热烈的拥抱 没有追到你时&#xff0c;我一直在路上 追到你时&#xff0c;我的人生就被你点燃 ——致所有的追光者 合肥先进光源国家重大科技基础设施项目及配套工程启动会刚开过&…

go学习之数组与Map

文章目录 一、数组1.为什么需要数组2.数组快速入门3、数组的定义和内存布局数组的使用数组的遍历数组的注意事项和细节数组的应用案例 4.slice切片1.基本介绍2.切片使用的三种方式way1way2way3 3.切片的注意事项4.string和slice 5.二维数组1.排序1&#xff09;排序的基本介绍2&…

uni-app实现获取未来七天时间和星期几功能

例子如下&#xff1a; HTML&#xff1a; <viewstyle"margin-top: 3%;width: 100%;height: 10vh;display: flex;justify-content: space-around;"><div v-for"(item,index) in same_week" :class"[same_dayitem.date? activ :,dis]"cl…

在虚拟机上安装win10/ubuntu的教程

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 一、下载软件资源 1、首先下载虚拟机Vmware_Pro17软件并正确安装&#xff1a;网盘链接 2、然后下载操作系统的镜像文件&#xff1a;MSDN, 我告诉你 - 做一个安静的工具站 二、在虚拟机上安装ubuntu系统 1…

EXP武器库编写

文章目录 pocsuite3工具SQL注入EXP布尔盲注优化最终优化 延时注入 phpstudy2016-2018-RCE利用DVWA文件上传metinfo_5.0.4EXPSQL-布尔盲注文件包含漏洞 定制SQLmaptamper脚本sqli-labs/less-26关卡分析 tamper脚本编写 python是黑客最喜欢的编程语言之一&#xff0c;但同时go语言…

基于AVR128单片机智能电风扇控制系统

一、系统方案 模拟的电风扇的工作状态有3种&#xff1a;自然风、常风及睡眠风。使用三个按键S1-S3设置自然风、常风及睡眠风。 再使用两个按键S4和S5&#xff0c;S4用于定时电风扇定时时间长短的设置&#xff0c;每按一次S4键&#xff0c;定时时间增加10秒&#xff0c;最长60秒…

C/C++程序员技术发展方向(强烈推荐!!)

大家好&#xff0c;我是阿Q。 今天这篇就是专门给现在还迷茫不知道自己到底要做什么方向C开发的同学们。 几年后回过头看的时候&#xff0c;你一定会感谢当初那个努力的自己&#xff01; C作为当下也非常流行的一个面向对象语言&#xff0c;有着非常多的应用&#xff0c;一定…

RHCSA 文件的上传下载(Linux-Windows)

目录 一、SCP 上传&#xff08;Windows--->Linux&#xff09;&#xff1a; 下载&#xff08;Linux--->Windows&#xff09;&#xff1a; 二、STFP 三、XFTP工具 一、SCP 上传&#xff08;Windows--->Linux&#xff09;&#xff1a; 在Windows本地端命令窗口中转…

基因组注释(Annotation)

基因组组装完成后&#xff0c;或者是完成了草图&#xff0c;就不可避免遇到一个问题&#xff0c;需要对基因组序列进行注释。注释之前首先得构建基因模型&#xff0c;有三种策略&#xff1a; 从头注释(de novo prediction)&#xff1a;通过已有的概率模型来预测基因结构&#…

【刷题】2023年第十四届蓝桥杯大赛软件类省赛C/C++大学A组真题

蓝桥杯2023年第十四届省赛真题-平方差 - C语言网 (dotcpp.com) 初步想法&#xff0c;x y2 − z2&#xff08;yz)(y-z) 即xa*b&#xff0c;ayz&#xff0c;by-z 2yab 即ab是2的倍数就好了。 即x存在两个因数之和为偶数就能满足条件。 但时间是&#xff08;r-l&#xff09;*x&am…

C语言回调函数与注册函数的使用

概述 在项目中&#xff0c;经常见到此写法&#xff0c;在此写个demo&#xff0c;方便其他工程师参阅。 开发环境&#xff1a;Visual Studio Community 2022 1、代码 #include <stdio.h>//封装库代码 typedef struct {bool status;void (*setStatus)(int status); }T_…