嗨玩手游网

原神

原神

角色扮演|

原神project手游是一款以日...

下载

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

▌说在前面:

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

· 如何设计线程池?

· 请手写一个简单线程池?

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

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

小伙伴的原话如下:

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

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

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

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

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

▌本文目录:

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

▌滴滴的面试真题分析:

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

这个是一个基础题,具体的答案,请参考尼恩的 《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 + 1和 2 * i + 2。

▌在堆中添加元素:

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

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

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

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

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

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

整体过程如下图:

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

▌在堆中删除元素:

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

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

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

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

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

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

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

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

▌说在最后:

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

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

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

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

▌11个技术圣经 PDF:

原神:3.6版本角色抽取优先级及命座抽取价值,五神将轮换上线

各位玩家大家好,欢迎来到游视姬!这里提供原神最新的前瞻资讯及玩法攻略,感谢您的关注!

原神3.6版本卡池角色随着前瞻直播的结束也终于实锤了,小草神纳西妲、妮露在上半卡池,白术、妮露在下半卡池,这四位角色客观来讲都不错,但在原石有限情况下,还是不得不做取舍,这里对四位角色整体简要分析下,希望帮助玩家们做出抽卡选择。

一、小草神纳西妲

小草神纳西妲在首次上线时流水创新高,可见她的实力之强,绝对是T0级角色。纳西妲是须弥版本体系的核心,拥有最强的挂草能力,她只需要长按E扫一圈,就能获得长达25秒的持续持草,并且伤害也不弱,还有就是大招能给队友最多提供250点元素精通,因此在所有草系配队都需要她。

小草神纳西妲的上线真是带飞了很多新老角色并形成了很多超强阵容,在当前环境下,各类“种门”都是主流玩法,拥有一个纳西妲是非常有必要的,而且0命性价比非常高,如果抽到2命更是提升巨大,因此3.6版本没有纳西妲的玩家非常建议抽一个,有了的话也非常建议补个2命。

二、妮露

妮露的定位是绽放反应的特化辅助,上线时曾不被看好,但在当前强度绝对T0级别,对群能力非常强。核心技能是被动天赋1的“丰馈的金杯”,元素战技的主要作用是触发被动天赋辅助前台草系或水系角色,非常适合萌新玩家,因为组成妮露绽放阵容的成本相对是最低的,阵容中的四个角色主要培养妮露就可以,其他的在战斗中只要不挂掉就行,而妮露的培养成本也比较低,只需要把角色和圣遗物等级拉满就可以,其他前期没有资源可以先不管。

妮露不是特别依赖专武和命座,抽到0命就能很好的组成妮露绽放队,当然专武对妮露是特别专的,提升也非常大,但原石不够也不用强抽,此外如果要抽命座的话,1命可以延长水环6秒,做到无缝E,这个命座对妮露的提升非常大,2命可以降低草抗35%,所以要想补命座的话建议可以优先补1命,然后是2命。

三、白术

白术的技能特点有盾有奶有增伤,集三个辅助能力于一体,白术可以灵活的穿梭于各个草反应队伍之前,他的加入无疑可以大大提升玩家们阵容的配队深度,此外在短时间内,应该也不会再推荐草系的生存角色。

另外白术不仅生存能力强,还对受到Q技能治疗的角色他所触发的部分元素反应伤害将会得到提升,这个机制的覆盖面会非常非常广,对于提纳里、妮露、艾尔海森、赛诺都将有大幅的提升。不过白术强度虽然看着不错,但作为首次UP,因此最好是等上线实测后再考虑是否抽取,但估计应该会是不错的。

四、甘雨

甘雨作为老牌璃月3C强力主C,有着超远距离的霜华矢射击,高贵的脱手锁面板大招, 0命就是一个技能机制没有短板的角色。无论是对单还是对群输出能力都是非常不错的。不过在当前环境下,甘雨目前的强度只能说是中规中矩,如果已经有了神里绫华的话玩家就不用考虑抽取啦!仅推荐甘雨厨抽取,另外没冰C的话也还是建议优先抽取神里绫华。

不过甘雨在大世界上还是挺好用的,因为甘雨独特的远程射击能力,在大世界打怪时可以完全不用接近怪物,能做到超远距离杀怪,可以真正体验什么是移动的炮台从命座上来讲,所以玩家原石充足的话抽1个0命的甘雨也还是不错的,如果要补命座的话,1命可以减低敌人的冰抗,间接地增加冰系伤害,还能恢复能量;4命增加敌人受到的伤害;6命在第一次释放E技能以后的重击不用蓄力时间,这些都对甘雨的提升比较大,抽命座的话可以考虑。

以上是原神3.6版本四位五星角色的抽取建议,目前也有很多平台在做关于这四位角色的抽取意愿统计,当前的统计结果大体是玩家们最想抽的是小草神纳西妲、其次是白术、然后是妮露,最后是甘雨。

此外随着小草神纳西妲在3.6版本的上线,应该也将迎来五神接连上线的时代,想抽神明的玩家原石准备起来吧!让我们一起期待3.6版本的到来吧!

FreeRTOS任务基础知识

FreeRTOS是一个开源的实时操作系统,主要用于嵌入式系统的开发。FreeRTOS的核心功能是提供多任务处理能力,即可以在一个处理器上同时运行多个程序,每个程序称为一个任务。FreeRTOS任务基础知识包括以下几个方面:

任务的创建:FreeRTOS提供了两种方式创建任务,一种是静态内存分配,即在编译时就确定任务的内存空间;另一种是动态内存分配,即在运行时根据需要分配任务的内存空间。创建任务需要指定任务的函数、名称、优先级、堆栈大小和控制块等参数。任务的删除:FreeRTOS提供了两种方式删除任务,一种是自删除,即任务在执行完毕后调用vTaskDelete(NULL)函数自动删除自己;另一种是外部删除,即其他任务或中断服务例程调用vTaskDelete(xTaskHandle)函数删除指定的任务。删除任务会释放其占用的内存空间和其他资源。任务的挂起和恢复:FreeRTOS提供了两种方式挂起和恢复任务,一种是软件方式,即调用vTaskSuspend(xTaskHandle)和vTaskResume(xTaskHandle)函数来暂停和恢复指定的任务;另一种是硬件方式,即通过外部事件(如按键、定时器等)触发中断服务例程来暂停和恢复指定的任务。挂起和恢复任务会改变其状态和调度顺序。任务的状态和优先级:FreeRTOS中的任务有四种状态,分别是就绪、运行、阻塞和挂起。就绪状态表示任务已经准备好运行,但还没有被调度器选中;运行状态表示任务正在执行;阻塞状态表示任务正在等待某个事件发生(如延时、信号量、队列等);挂起状态表示任务被暂停。每个任务都有一个优先级,用来决定其在就绪队列中的位置。优先级越高的任务越容易被调度器选中。任务的堆栈和控制块:FreeRTOS中的每个任务都有一个堆栈和一个控制块。堆栈是用来存储任务的局部变量、函数参数、返回地址等信息的内存空间;控制块是用来存储任务的状态、优先级、堆栈指针等信息的数据结构。堆栈和控制块可以使用静态或动态内存分配方式创建。任务的调度器和抢占机制:FreeRTOS中的调度器是一个函数,用来选择下一个要运行的任务。调度器可以在两种情况下被触发,一种是时间片到期,即当前运行的任务已经占用了一定时间的处理器资源;另一种是优先级抢占,即出现了一个优先级更高的就绪任务。时间片到期时,调度器会选择优先级最高且最早进入就绪队列的任务;优先级抢占时,调度器会立即切换到优先级最高的就绪任务。
更多攻略
游戏推荐
更多+