当前位置: 首页 > news >正文

并发编程-延时队列DelayQueue

数据结构学习网站:

Data Structure Visualization

思维导图

      

DelayQueue (延时队列)

          DelayQueue 是一个支持延时获取元素的阻塞队列 , 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。
        延迟队列的特点是: 不是先进先出,而是会按照延迟时间的 长短来排序,下一个即将执行的任务会排到队列的最前面。
它是无界队列,放入的元素必须实现 Delayed 接口,而 Delayed 接口又继承了 Comparable 接
口,所以自然就拥有了比较和排序的能力,代码如下:
public interface Delayed extends Comparable<Delayed> {//getDelay 方法返回的是“还剩下多长的延迟时间才会被执行”,//如果返回 0 或者负数则代表任务已过期。//元素会根据延迟时间的长短被放到队列的不同位置,越靠近队列头代表越早过期。long getDelay(TimeUnit unit);}

DelayQueue使用

DelayQueue 实现延迟订单
在实现一个延迟订单的场景中,我们可以定义一个 Order 类,其中包含订单的基本信息,例如订单编 号、订单金额、订单创建时间等。同时,我们可以让 Order 类实现 Delayed 接口,重写 getDelay 和 compareTo 方法。在 getDelay 方法中,我们可以计算订单的剩余延迟时间,而在 compareTo 方法 中,我们可以根据订单的延迟时间进行比较。
下面是一个简单的示例代码,演示了如何使用 DelayQueue 来实现一个延迟订单的场景:
public class DelayQueueExample {public static void main(String[] args) throws InterruptedException {DelayQueue<Order> delayQueue = new DelayQueue<>();// 添加三个订单,分别延迟 5 秒、2 秒和 3 秒delayQueue.put(new Order("order1", System.currentTimeMillis(), 5000));delayQueue.put(new Order("order2", System.currentTimeMillis(), 2000));delayQueue.put(new Order("order3", System.currentTimeMillis(), 3000));// 循环取出订单,直到所有订单都被处理完毕while (!delayQueue.isEmpty()) {Order order = delayQueue.take();System.out.println("处理订单:" + order.getOrderId());}}static class  Order implements Delayed{private String orderId;private long createTime;private long delayTime;public Order(String orderId, long createTime, long delayTime) {this.orderId = orderId;this.createTime = createTime;this.delayTime = delayTime;}public String getOrderId() {return orderId;}@Overridepublic long getDelay(TimeUnit unit) {long diff = createTime + delayTime - System.currentTimeMillis();return unit.convert(diff, TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {long diff = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);return Long.compare(diff, 0);}}
}
        由于每个订单都有不同的延迟时间,因此它们将会按照延迟时间的顺序被取出。当延迟时间到达时, 对应的订单对象将会被从队列中取出,并被处理。

DelayQueue原理

数据结构
 //用于保证队列操作的线程安全private final transient ReentrantLock lock = new ReentrantLock();// 优先级队列,存储元素,用于保证延迟低的优先执行private final PriorityQueue<E> q = new PriorityQueue<E>();// 用于标记当前是否有线程在排队(仅用于取元素时) leader 指向的是第一个从队列获取元素阻塞的线程private Thread leader = null;// 条件,用于表示现在是否有可取的元素 当新元素到达,或新线程可能需要成为leader时被通知private final Condition available = lock.newCondition();public DelayQueue() {}public DelayQueue(Collection<? extends E> c) {this.addAll(c);}

入队put方法

    public void put(E e) {offer(e);}public boolean offer(E e) {final ReentrantLock lock = this.lock;lock.lock();try {// 入队q.offer(e);if (q.peek() == e) {// 若入队的元素位于队列头部,说明当前元素延迟最小// 将 leader 置空leader = null;// available条件队列转同步队列,准备唤醒阻塞在available上的线程available.signal();}return true;} finally {lock.unlock(); // 解锁,真正唤醒阻塞的线程}}

出队take方法

 public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {for (;;) {E first = q.peek();// 取出堆顶元素( 最早过期的元素,但是不弹出对象)if (first == null)// 如果堆顶元素为空,说明队列中还没有元素,直接阻塞等待available.await();//当前线程无限期等待,直到被唤醒,并且释放锁。else {long delay = first.getDelay(NANOSECONDS);// 堆顶元素的到期时间if (delay <= 0)// 如果小于0说明已到期,直接调用poll()方法弹出堆顶元素return q.poll();// 如果delay大于0 ,则下面要阻塞了// 将first置为空方便gcfirst = null;// 如果有线程争抢的Leader线程,则进行无限期等待。if (leader != null)available.await();else {// 如果leader为null,把当前线程赋值给它Thread thisThread = Thread.currentThread();leader = thisThread;try {// 等待剩余等待时间available.awaitNanos(delay);} finally {// 如果leader还是当前线程就把它置为空,让其它线程有机会获取元素if (leader == thisThread)leader = null;}}}}} finally {// 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程if (leader == null && q.peek() != null)// available条件队列转同步队列,准备唤醒阻塞在available上的线程available.signal();// 解锁,真正唤醒阻塞的线程lock.unlock();}}
1. 当获取元素时,先获取到锁对象。
2. 获取最早过期的元素,但是并不从队列中弹出元素。
3. 最早过期元素是否为空,如果为空则直接让当前线程无限期等待状态,并且让出当前锁对象。
4. 如果最早过期的元素不为空
5. 获取最早过期元素的剩余过期时间,如果已经过期则直接返回当前元素
6. 如果没有过期,也就是说剩余时间还存在,则先获取Leader对象,如果Leader已经有线程在处理,则当前线程进 行无限期等待,如果Leader为空,则首先将Leader设置为当前线程,并且让当前线程等待剩余时间。
7. 最后将Leader线程设置为空
8. 如果Leader已经为空,并且队列有内容则唤醒一个等待的队列。

如何选择适合的阻塞队列

 选择策略

通常我们可以从以下 5 个角度考虑,来选择合适的阻塞队列:
功能
        第 1 个需要考虑的就是 功能层面 ,比如是否需要阻塞队列帮我们排序,如 优先级排序、延迟执行 等。如果有这个需要,我们就必须选择类似于 PriorityBlockingQueue 之类的有排序能力的阻塞队 列。
容量
        第 2 个需要考虑的是 容量 ,或者说 是否有存储的要求 ,还是只需要“直接传递”。在考虑这一点
的时候,我们知道前面介绍的那几种阻塞队列,有的是 容量固定的,如 ArrayBlockingQueue ;有的 默认是 容量无限的,如 LinkedBlockingQueue ;而有的里面 没有任何容量,如
SynchronousQueue ;而对于 DelayQueue 而言,它的容量固定就是 Integer.MAX_VALUE 。所以不同阻塞队列的容量是千差万别的, 我们需要根据任务数量来推算出合适的容量 ,从而去选取合适的 BlockingQueue。
能否扩容
        第 3 个需要考虑的是 能否扩容 。因为有时我们并不能在初始的时候很好的准确估计队列的大小, 因为业务可能有高峰期、低谷期。如果一开始就固定一个容量,可能无法应对所有的情况,也是不合适的,有可能需要动态扩容。如果我们需要动态扩容的话,那么就不能选择 ArrayBlockingQueue , 因为它的容量在创建时就确定了,无法扩容。相反 PriorityBlockingQueue 即使在指定了初始容量 之后,后续如果有需要,也可以自动扩容 。所以 我们可以根据是否需要扩容来选取合适的队列。
内存结构
        第 4 个需要 考虑的点就是内存结构 。我们分析过 ArrayBlockingQueue 的源码,看到了它的内部 结构是“数组” 的形式。和它不同的是, LinkedBlockingQueue 的内部是用链表 实现的,所以这里就需要我们考虑到, ArrayBlockingQueue 没有链表所需要的“节点”,空间利用率更高 。所以如果我们对性能有要求可以从内存的结构角度去考虑这个问题。
性能
        第 5 点就是 从性能的角度去考虑 。比如 LinkedBlockingQueue 由于拥有两把锁,它的操作粒度更细 在并发程度高的时候,相对于 只有一把锁的 ArrayBlockingQueue 性能会更好 。另外, SynchronousQueue 性能往往优于其他实现 ,因为 它只需要“直接传递” ,而不需要存储的过程。 如果我们的场景需要直接传递的话,可以优先考虑 SynchronousQueue

 线程池对于阻塞队列的选择

 线程池有很多种,不同种类的线程池会根据自己的特点,来选择适合自己的阻塞队列。
Executors 类下的线程池类型:
FixedThreadPool (SingleThreadExecutor 同理)选取的是 LinkedBlockingQueue
CachedThreadPool 选取的是 SynchronousQueue
ScheduledThreadPool SingleThreadScheduledExecutor同理)选取的是延迟队列

相关文章:

并发编程-延时队列DelayQueue

数据结构学习网站&#xff1a; Data Structure Visualization 思维导图 DelayQueue &#xff08;延时队列&#xff09; DelayQueue 是一个支持延时获取元素的阻塞队列 &#xff0c; 内部采用优先队列 PriorityQueue 存储元素&#xff0c;同时元素必须实现 Delayed 接口&#x…...

Python之哈希表-遍历和有序性

Python之哈希表-遍历和有序性 有序性 字典元素是按照key的hash值无序存储的。 但是&#xff0c;有时候我们却需要一个有序的元素顺序&#xff0c;Python 3.6之前&#xff0c;使用OrderedDict类可以做到&#xff0c;3.6开 始dict自身支持。 d1 {b:33, c:True, d:[1], f:234…...

Oracle数据库完整卸载的完整步骤

时间&#xff1a;2023-03-15来源&#xff1a;系统城装机大师作者&#xff1a;佚名 1、停止所有Oracle服务 进入计算机管理&#xff0c;在服务中&#xff0c;找到oracle开头的所有服务&#xff0c;右击选择停止。 快捷键&#xff1a;ctrlshiftesc打开任务管理器 文章来源 Or…...

HP OfficeJet Pro 8020 如何更换碳粉盒

环境&#xff1a; HP OfficeJet Pro 8020 问题描述&#xff1a; HP OfficeJet Pro 8020 如何更换碳粉盒 解决方案&#xff1a; 更换碳粉盒 更换所有墨水不足的碳粉盒或空碳粉盒。 1.打开前挡盖&#xff0c;然后提起碳粉盒检修门。 打开打印机门 2.等待笔架停止后再继续操作…...

【Javascript】基础数据类型

目录 基础数据类型 1.number 字面量声明 数字对象方式声明 整数判断 指定返回小数位数 NaN-表示非数字值 浮点精度 解决误差 String 字面量声明 数字对象声明 连接运算符 获取长度 大小写转换 转换成大写 转换成小写 ​编辑 移除空白 获取单字符 ​编辑 截…...

【C语言】进阶——程序编译

目录 一&#xff1a;&#x1f512;程序环境 程序的翻译环境和执行环境 &#x1f4a1;1.1翻译环境 预编译阶段&#xff1a; 编译阶段&#xff1a; 汇编阶段&#xff1a; 链接阶段&#xff1a; &#x1f4a1;1.2运行环境 二&#xff1a;&#x1f512;预处理详解 &…...

记录阿里云服务器(Centos7.9)部署Thingsboard(3.4.2)遇到的一些问题

记录编译Thingsboard遇到的一些问题 部署了一个thingsboard项目到阿里云服务器上&#xff0c;历时十一天&#xff0c;遇到了很多困难&#xff0c;国内关于Thingsboard的资料确实很少&#xff0c;所以想着写一篇博客记录一下&#xff0c;或许能够给以后编译遇到类似问题的人一些…...

docker更新容器映射端口

一个容器已经暴露了一个端口被外界使用&#xff0c;但是这个端口被公司不允许使用&#xff0c;需要修改为其他的端口&#xff0c;怎么办&#xff1f; 1、删除原容器&#xff0c;重启新容器 删除已启动容器&#xff0c;从镜像重启新容器。2、修改原容器配置文件 3、生成镜像&…...

Pr快捷键

Pr快捷键 以下快捷键都是在英文输入法下 一、隐藏顶部项目信息 Ctrl\ 注意&#xff1a;是反斜杠&#xff0c;回车上面的按键二、单独放大窗口 选中面板按~键三、放大/缩小时间轴素材 \四、自动选中素材 序列菜单-选择跟随播放指示器五、快速定位间隙 SHIFT鼠标拖动素材 …...

94. 递归实现排列型枚举

题目&#xff1a; 94. 递归实现排列型枚举 - AcWing题库 思路&#xff1a; 1.全排列问题&#xff08;坑位问题&#xff09;---->递归搜索树---->用dfs深度优先搜索。 2. u表示当前坑位&#xff0c;state[u]表示坑位u存储的数据。因为不同坑位的数据不可以重复&#…...

白水三佳电脑ERP部署

安装宝塔面板&#xff0c;有这个方便很多&#xff0c;可以省下3天的环境部署时间。 移动端&#xff0c; 先取移动版的压缩包&#xff0c;上传至服务器/www/wwwroot/目录下面&#xff0c;直接解压到当前目录后会生成/www/wwwroot/m/的目录&#xff0c;移动版就在这里面了。以下…...

电流监测芯片SGM8199A2应用电路设计

SGM8199是一系列具有电压输出功能的双向电流监测芯片&#xff0c;用于监测共模电压范围内分流电阻上的压降&#xff0c;而不受电源电压的影响。该器件具有-0.1V至26V的宽共模电压范围输入。低偏移使得在监测电流时允许分流器上的满量程最大压降为10mV。SGM8199系列提供三种固定…...

第十五章 I/O输入输出

15,1输入输出流 流是一组有序的数据序列&#xff0c;根据操作的类型&#xff0c;可分为输入流和输出流两种。I/O(Input/Output,(输出)流提供了一条通道程序&#xff0c;可以使用这条通道把源中的字节序列送到目的地。虽然 I/O 流疆盘文件存取有关&#xff0c;但是程序的源和目的…...

进程(0)——计算机的中的软硬件【Linux】

进程&#xff08;0&#xff09;——计算机的中的软硬件【Linux】 一.硬件&#xff1a;1.1 冯诺依曼结构&#xff1a;1.2 存储金字塔1.2.1输入设备和存储器&#xff1a;1.2.2输出设备和存储器&#xff1a; 二.软件&#xff1a;2.1 操作系统2.1.1 如何理解管理&#xff1a; 博主自…...

Python中if not使用教程

大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 python中判断变量是否为None三种写法&#xff1a; 1、if x is None 2、if not x 3、if not x is None 理解成 if not (x is None) 结果是和1相反的 python中None、fals…...

Jmeter性能测试 —— jmeter之使用ServerAgent监控服务器

ServerAgent 性能测试时我们关注的重要指标是&#xff1a;并发用户数&#xff0c;TPS&#xff0c;请求成功率&#xff0c;响应时间&#xff0c;服务器的CPU&#xff0c;memory&#xff0c; I/O disk等。Jmeter的聚合报告可以查看并发数、吞吐量、请求成功率、响应时间等&#…...

C# Winform编程(7)文件处理技术

文件处理技术 System.IO命名空间System.IO命名空间常用的类System.IO命名空间常用的枚举 File类的常用方法File类的常用静态方法FileInfo类的常用方法File类和FileInfo类的区别文件夹类Directory的常用方法文件流类FileStream文件的读写读写二进制流读写内存流 System.IO命名空…...

前端工作方式要换了?HTMX简介:无需JavaScript的动态HTML

HTMX允许你使用扩展的HTML语法代替 JavaScript 来实现交互性。HTMX 在标记中直接为你提供HTTP 交互&#xff0c;并支持许多其他交互需求&#xff0c;无需求助于 JavaScript。这是一个有趣的想法&#xff0c;可能最终会影响到web前端的工作方式。让我们看看如何使用HTMX以及它的…...

动手学深度学习—使用块的网络VGG(代码详解)

目录 1. VGG块2. VGG网络3. 训练模型 1. VGG块 经典卷积神经网络的基本组成部分是下面的这个序列&#xff1a; 1.带填充以保持分辨率的卷积层&#xff1b; 2.非线性激活函数&#xff0c;如ReLU&#xff1b; 3.汇聚层&#xff0c;如最大汇聚层。 定义网络块&#xff0c;便于我…...

性能优化:JIT即时编译与AOT提前编译

优质博文&#xff1a;IT-BLOG-CN 一、简介 JIT与AOT的区别&#xff1a; 两种不同的编译方式&#xff0c;主要区别在于是否处于运行时进行编译。 JIT:Just-in-time动态(即时)编译&#xff0c;边运行边编译&#xff1a;在程序运行时&#xff0c;根据算法计算出热点代码&#xf…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态

前言 在人工智能技术飞速发展的今天&#xff0c;深度学习与大模型技术已成为推动行业变革的核心驱动力&#xff0c;而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心&#xff0c;系统性地呈现了两部深度技术著作的精华&#xff1a;…...

【若依】框架项目部署笔记

参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作&#xff1a; 压缩包下载&#xff1a;http://download.redis.io/releases 1. 上传压缩包&#xff0c;并进入压缩包所在目录&#xff0c;解压到目标…...