在阿里面试官面前现场手撕DelayQueue源码!

2023-12-01 21:40

本文主要是介绍在阿里面试官面前现场手撕DelayQueue源码!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

如果不想在世界上虚度一生,那就要学习一辈子。
——高尔基

0 前言

延迟元素的无边界阻塞队列,在该队列中,仅当元素的延迟到期时才可以使用它.
队首是该 Delayed 元素,其延迟在过去最远过期.
如果没有延迟已经过期,就没有head, poll将返回null.
当元素的getDelay(TimeUnit.NANOSECONDS)方法返回的值小于或等于零时,就会发生过期.
即使未到期的元素无法使用take或poll删除,它们也被视为普通的元素。 例如,size方法返回过期和未过期元素的计数.
此队列不允许空元素.
该类及其迭代器实现集合和迭代器接口的所有可选方法。方法Iterator()中提供的迭代器不能保证以任何特定的顺序遍历DelayQueue中的元素.

此类是Java Collections Framework的成员.

1 继承体系


  • 该队列里的元素必须实现Delayed接口才能入队

    混合式的接口,用于标记在给定延迟后应作用的对象。此接口的实现还必须定义一个compareTo方法,该方法提供与其getDelay方法一致的顺序.

2 属性


  • PriorityQueue队列里的元素会根据某些属性排列先后的顺序,这里正好可以利用Delayed接口里的getDelay的返回值来进行排序,delayQueue其实就是在每次往优先级队列中添加元素,然后以元素的delay/过期值作为排序的因素,以此来达到先过期的元素会拍在队首,每次从队列里取出来都是最先要过期的元素

  • 指定用于等待队首元素的线程。 Leader-Follower模式的变体用于最大程度地减少不必要的定时等待.当一个线程成为leader时,它仅等待下一个延迟过去,但是其他线程将无限期地等待.leader线程必须在从take()或poll(…)返回之前向其他线程发出信号,除非其他线程成为过渡期间的leader。.每当队首被具有更早到期时间的元素替换时,leader字段都会被重置为null来无效,并且会发出一些等待线程(但不一定是当前leader)的信号。 因此,等待线程必须准备好在等待时获得并失去leader能力.

  • 当更新的元素在队首变得可用或新的线程可能需要成为 leader 时,会发出条件信号

3 构造方法

3.1 无参

  • 创建一个新的 DelayQueue,它初始是空的

3.2 有参

  • 创建一个DelayQueue,初始包含Delayed实例的给定集合的元素。

4 新增数据

先看看继承自 BlockingQueue 的方法

put

  • 将指定的元素插入此延迟队列。 由于队列无界,因此此方法将永远不会阻塞.

    可以看到 put 调用的是 offer

DelayQueue#offer

  • 将指定的元素插入此延迟队列

执行流程

1.加锁
2.元素添加到优先级队列中
3.检验元素是否为队首,是则设置 leader 为null, 并唤醒一个消费线程
4.解锁

其内部调用的是 PriorityQueue 的 offer 方法

PriorityQueue#offer

将指定的元素插入此优先级队列.

public boolean offer(E e) {// 若元素为 null,抛NPEif (e == null)throw new NullPointerException();// 修改计数器加一modCount++;int i = size;// 如果队列大小 > 容量 if (i >= queue.length)// => 扩容grow(i + 1);size = i + 1;// 若队列空,则当前元素正好处于队首if (i == 0)queue[0] = e;else// 若队列非空,根据优先级排序siftUp(i, e);return true;
}
执行流程
  1. 元素判空
  2. 队列扩容判断
  3. 根据元素的 compareTo 方法进行排序,希望最终排序的结果是从小到大的,因为想让队首的都是过期的数据,需要在 compareTo 方法实现.

5 取数据

take

检索并删除此队列的头,如有必要,请等待直到延迟过期的元素在此队列上可用

    public E take() throws InterruptedException {final ReentrantLock lock = this.lock;// 获取可中断锁lock.lockInterruptibly();try {for (;;) {// 从优先级队列中获取队首E first = q.peek();if (first == null)// 队首为 null,说明无元素,当前线程加入等待队列,并阻塞available.await();else {// 获取延迟时间long delay = first.getDelay(NANOSECONDS);if (delay <= 0)// 已到期,获取并删除头部元素return q.poll();first = null; // 在等待时不要保留引用if (leader != null)available.await();else {Thread thisThread = Thread.currentThread();leader = thisThread;try {// 线程节点进入等待队列available.awaitNanos(delay);} finally {if (leader == thisThread)leader = null;}}}}} finally {// 若leader == null且还存在元素,则唤醒一个消费线程if (leader == null && q.peek() != null)available.signal();// 解锁lock.unlock();}}

执行流程

  1. 加锁
  2. 取出优先级队列的队首
  3. 若队列为空,阻塞
  4. 若队首非空,获得这个元素的delay时间值,如果first的延迟delay时间值为0的话,说明该元素已经到了可以使用的时间,调用poll方法弹出该元素,跳出方法
  5. 若first的延迟delay时间值非0,释放元素first的引用,避免内存泄露
  6. 循环以上操作,直至return

take 方法是会无限阻塞,直到队头的过期时间到了才会返回.
如果不想无限阻塞,可以尝试 poll 方法,设置超时时间,在超时时间内,队头元素还没有过期的> 话,就会返回 null.

6 解密 leader 元素

leader 是一个Thread元素,表示当前获取到锁的消费者线程.

  • 以take代码段为例

若 leader 非 null,说明已有消费者线程获取锁,直接阻塞当前线程.

若 leader 为 null,把当前线程赋给 leader,并等待剩余的到期时间,最后释放 leader.
这里假设有多个消费者线程执行 take 取数据,若没有leader != null 判断,这些线程都会无限循环,直到返回第一个元素,这显然很浪费系统资源. 所以 leader 在这里相当于一个线程标识,避免消费者线程的无脑竞争.

  • 注意这里因为first是队首的引用,阻塞时会有很多线程同时持有队首引用,可能导致内存溢出,所以需要手动释放.

7 总结

DelayQueue 使用排序和超时机制即实现了延迟队列.充分利用已有的 PriorityQueue 排序功能,超时阻塞又恰当好处的利用了锁的等待,在已有机制的基础上进行封装.在实际开发中,可以多多实践这一思想,使代码架构具备高复用性.

这篇关于在阿里面试官面前现场手撕DelayQueue源码!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/442786

相关文章

linux配置podman阿里云容器镜像加速器详解

《linux配置podman阿里云容器镜像加速器详解》本文指导如何配置Podman使用阿里云容器镜像加速器:登录阿里云获取专属加速地址,修改Podman配置文件并移除https://前缀,最后拉取镜像... 目录1.下载podman2.获取阿里云个人容器镜像加速器地址3.更改podman配置文件4.使用po

java 恺撒加密/解密实现原理(附带源码)

《java恺撒加密/解密实现原理(附带源码)》本文介绍Java实现恺撒加密与解密,通过固定位移量对字母进行循环替换,保留大小写及非字母字符,由于其实现简单、易于理解,恺撒加密常被用作学习加密算法的入... 目录Java 恺撒加密/解密实现1. 项目背景与介绍2. 相关知识2.1 恺撒加密算法原理2.2 Ja

Nginx屏蔽服务器名称与版本信息方式(源码级修改)

《Nginx屏蔽服务器名称与版本信息方式(源码级修改)》本文详解如何通过源码修改Nginx1.25.4,移除Server响应头中的服务类型和版本信息,以增强安全性,需重新配置、编译、安装,升级时需重复... 目录一、背景与目的二、适用版本三、操作步骤修改源码文件四、后续操作提示五、注意事项六、总结一、背景与

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

8种快速易用的Python Matplotlib数据可视化方法汇总(附源码)

《8种快速易用的PythonMatplotlib数据可视化方法汇总(附源码)》你是否曾经面对一堆复杂的数据,却不知道如何让它们变得直观易懂?别慌,Python的Matplotlib库是你数据可视化的... 目录引言1. 折线图(Line Plot)——趋势分析2. 柱状图(Bar Chart)——对比分析3

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法

《springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法》:本文主要介绍springboot整合阿里云百炼DeepSeek实现sse流式打印,本文给大家介绍的非常详细,对大... 目录1.开通阿里云百炼,获取到key2.新建SpringBoot项目3.工具类4.启动类5.测试类6.测