队列总结(六)DelayQueue

延迟队列DelayQueue原理 前言 什么是DelayQueue(延时队列) DelayQueue 是一个通过PriorityBlockingQueue实现延迟获取元素的无界队列无界阻塞队列,其中添加进该队列的元素必须实现Delayed接口(指定延迟时间),而且只有在延迟期满后才能从中提取元素。 什么是PriorityBlockingQueue(优先队列) PriorityBlockingQueue是一个支持优先级的无界阻塞队列,队列的元素默认情况下元素采用自然顺序升序排列,或者根据构造队列时提供的 Comparator 进行排序,具体取 阅读详情

延时队列DelayQueue

延迟元素的无界阻塞队列,其中元素只能在其延迟到期后才能获取。

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {...}

public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}

队列中的元素必须实现Delayed 接口,该接口继承Comparable接口,并指定了获取元素到期时间的方法。这里提供一个简单的基础实现例子:

public class DelayElem implements Delayed {
    /**
     * 延迟时间
     */
    private final long delay;
    /**
     * 到期时间
     */
    private final long expire;
    /**
     * 数据
     */
    private final String msg;

    public DelayElem(long delay, String msg) {
        this.delay = delay;
        this.msg = msg;
        //到期时间 = 当前时间+延迟时间
        this.expire = System.currentTimeMillis() + this.delay;
    }

    /**
     * 需要实现的接口,获得延迟时间
     * 用过期时间-当前时间
     *
     * @param unit 时间单位
     * @return 延迟时间
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 用于延迟队列内部比较排序
     * 当前时间的延迟时间 - 比较对象的延迟时间
     *
     * @param o 比较对象
     * @return 结果
     */
    @Override
    public int compareTo(Delayed o) {
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }

    @Override
    public String toString() {
        return "DelayElem{" +
                "delay=" + delay +
                ", expire=" + expire +
                ", msg='" + msg + '\'' +
                '}';
    }
}

DelayElem 的 compareTo 方法实现必须合理,因为DelayQueue中存储元素实际使用的是PriorityQueue优先级队列,是依赖于compareTo 方法进行排序的。

这里不要认为到期时间短或已过期的元素就一定在队列头部(虽然这可能比较契合大多场景),一些特殊需求下,可能compareTo 更侧重于其他属性,那么过期的元素可能并不先出队。如修改上面DelayElem的compareTo 方法:

    @Override
    public int compareTo(Delayed o) {
        DelayElem d = (DelayElem) o;
        return (this.msg.compareTo(d.getMsg()));
    }

然后写个测试

    public static void main(String[] args) {
        DelayQueue<DelayElem> deque = new DelayQueue<>();
        DelayElem d1 = new DelayElem(2000, "c");
        DelayElem d2 = new DelayElem(4000, "a");
        deque.offer(d1);
        deque.offer(d2);
        System.err.println(deque.poll());
        System.err.println(deque.poll());
    }
 // 输出结果如下
 // mainDelayElem{delay=4000, expire=1634798982416, msg='a'}
 // mainDelayElem{delay=2000, expire=1634798980416, msg='c'}

继续聊DelayQueue,简单看一下核心源码

    private final transient ReentrantLock lock = new ReentrantLock();
    private final Condition available = lock.newCondition();
    // 用来实际存储数据的优先级队列
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    // 线程模型中的leader-follower模式
    private Thread leader = null;

只有这4个属性,其中leader 使用了leader-follower模式,查阅后在这里简单描述一下:

Leader-follower线程模型,其出现是为了解决“ 单线程接受请求,线程池线程处理请求 ”模式下,线程上下文切换以及线程间通信数据拷贝的开销,并且不需要维护一个队列。
在Leader-follower线程模型中每个线程有三种模式,leader,follower,processing。Leader-follower线程模型,一开始会创建一个线程池,并且会选取一个线程作为leader线程,leader线程负责监听网络请求,其它线程为follower处于waiting状态,当leader线程接受到一个请求后,会释放自己作为leader的权利,然后从follower线程中选择一个线程进行激活,然后激活的线程被选择为新的leader线程作为服务监听,然后老的leader则负责处理自己接受到的请求(现在老的leader线程状态变为了processing),处理完成后,状态从processing转换为follower。
可知这种模式下接受请求和进行处理使用的是同一个线程,这避免了线程上下文切换和线程通讯数据拷贝。

继续看添加元素的方法offer与获取元素的方法poll

    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            /**
             * 这里如果新插入的元素位于队列头部,无论是替代原头部还是作为队列中唯一的元素,
             * 都意味着所有正在阻塞,尝试获取头元素的线程需要重新获取头部元素
             */
            if (q.peek() == e) {
                leader = null;
                // 等待队列是有序的FIFO先进先出队列,首先激活的一定是leader线程
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e);
    }

该队列是无界队列,offer方法永远不会发生阻塞,所以我们需要关心的是poll(take方法逻辑看起来比poll明朗一些,两者思路是一致的,所以我选取take进行分析),leader-follower模式也是针对于多个线程并发阻塞获取的情景。

另外,我们发现其实唤醒操作available.signal(),只发生在两种情况下:

  1. 队列中新增了元素,并且该元素替换了原头元素,需要唤醒获取线程重新获取头元素
  2. 某个获取线程成功获取到元素或超时退出后,释放leader标记,唤醒下一个线程使其成为新的leader
    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)
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    // 说明只有leader线程才有执行权
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        		// 表示当前线程成功作为leader获取到了头部元素,唤醒下一个线程
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

分析一下:假设队列中存在元素,但未到期,暂不可获取时,其他多个线程进行尝试获取

  1. 第一个进入take的线程,当前其他无排队线程,leader为初始值null,之后当前线程成为leader,在当前线程未释放leader之前,即leader != null,其他后续进入的线程直接阻塞,直至被唤醒。
  2. leader等待一定时间后自动苏醒,成功获取到元素,或者在等待期间,队列中头元素发生了变化,被添加线程唤醒。唤醒(被唤醒的不一定是leader线程哦)后,重新尝试获取新的头部元素。
  3. leader每次醒来都会将leader字段置null,之后如果能成功获取,则会释放leader,唤醒下一个线程,功成身退。否则将再次将leader指向自己,继续等待。
  4. 被leader唤醒的线程醒来后,重复以上逻辑,如果需要等待,则此时leader == null,该线程会将leader指向自己,称为新的leader。

那么leader-follwer模式的优势在哪呢?
好处在于,leader线程等待指定时间,其他线程无限阻塞,leader醒后唤醒其他等待者(即等待队列中有人在排队时,其他人可以放心一直等待,而不是等待一段时间后就自己醒来检测一次),假设没有leader,每个排队线程等到队列元素到期可用时,都会醒来一次,然而只有一个线程能成功获取,这是没有必要的,类似惊群效应。
说的不是很明白,可以自己想象一下,假设不设置leader,take方法该怎么写,有什么问题

好像就这样完了吧。

DelayQueue延时队列的使用 一、简介 DelayQueue 是一个无界阻塞队列,添加进去的元素必须实现Delayed接口,而且只有在延迟期满时才能从中提取元素。 二、使用 实现Delayed接口类 package delayqueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class... 阅读详情

相关推荐

【Java多线程】JUC之并发容器—深入剖析并发队列DelayQueue(六)

- **DelayQueue**: 一个`内部使用优先级队列PriorityQueue(二叉小顶堆)`实现**延迟读取** 的`无界阻塞队列`,该队列中每个元素均有*过期时间*,当从队列获取元素时,`只有过期元素才会出队列`。`队列头元素是最块要过期的元素`。**内部使用ReentrantLock和Condition实现。** - 使用**PriorityQueue**来实现队列,所以以`二叉小顶堆`结构维**护元素顺序**,**这样`剩余时间最小的元素就在堆顶`,每次出队其实就是删除剩余时间≤0的最小元

墩墩分墩 722

JUC阻塞队列(四):DelayQueue

因为 DelayQueue 底层是基于 PriorityQueue 实现的,也就是基于二叉堆实现的,所以。DelayQueue 是一个延迟队列,生产者写入一个数据,这个数据具有被直接消费的延迟时间,DelayQueue 是一个无界的队列,存储数据的数组可以动态扩容,所以生产者不需要关注。二叉堆结构每次获取的是堆顶数据,在比较时,根据延迟时间进行比较,延迟时间剩余端的放。该方法功能是取数据,取堆顶数据,若取不到数据,则直接抛出异常。注意:若堆顶数据的延迟时间还没到达,则取不到数据,也会抛出异常。

liang8999的博客 677

JDK自带的延迟队列——DelayQueue

原理:DelayQueue是基于阻塞队列实现的,是JDK自带延迟队列。 优点:使用成本最低,而且不依赖任何第三方服务,减少了网络交互。 缺点:需要占用JVM内存,在数据量非常大的情况下可能会有问题。

kersixy的博客 2013

DelayQueue延时任务队列总结和实践

DelayQueue里面是一个无界的BlockingQueue,且有一个优先级队列 而且该优先级队列里面的实现是堆排序,这样每插入一个新的任务,都可以立即调整任务的顺序。 DelayQueue里面放的元素必须为实现了Delayed接口的任务。 里面有两个核心方法 getDelay 用于判断任务是否到期,如果是返回-1 表示任务已经到期 我这里用**(创建时间+延期时间-当前时间**)来做判断 c...

hello world 3793

基于大疆TSDK与Pix4D的热红外数据工程化处理:从RJPG到温度正射影像的完整工作流

本文详细介绍了基于大疆TSDK与Pix4D的热红外数据工程化处理完整工作流。通过Python脚本自动化解析RJPG格式的热红外图像,提取并转换其中的原始辐射数据为包含真实温度信息的TIFF文件,再结合Pix4Dmapper进行批量拼接,最终生成可直接用于GIS分析的温度正射影像图。该流程解决了手动处理效率低下、参数校准复杂等难题,实现了从原始数据到定量分析成果的高效、准确、可重复生产。

weixin_29161785的博客 377

DETR系列文章之–Co-DETR

Co-DETR,并行训练多个辅助头,以一对多的标签分配方式进行监督从辅助头中提取正样本坐标,为解码器中的正样本的训练效率进行额外的定制化正样本query.一对一标签分配:每个GT框分配给一个特定的query(DETR),较少的正query导致低效训练,从编码器生成的潜在表示和解码器中的注意力学习分析这一问题。一对多标签分配:每个GT框分配给检测器输出的多个proposal/anchor,依赖NMS或者anchor生成(ATSS,RetinaNet,FCOS,PAA)。

weixin_43731232的博客 2117

Java并发编程之DelayQueue阻塞队列详解

简介DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。DelayQueue非常有用,可以运用在以下两个应用场景: 缓存系统的设计:使用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦...

Stay Hungry, Stay Foolish 1万+

PCB之AD层次化原理图设计

PCB之AD层次化(模块化)原理图设计 软件版本:AD17.1.9(bBuild 592) 先上效果图: 可以看到,图中有RS485和W5500这两个部分是复用了原理图,当项目中需要多个功能相同的电路时,这种设计就可以大大节约PCB布局布线的时间,方法后面介绍; 1.首先确定好项目中个部分电路的功能模块 不同功能的电路分开不同原理图画,然后给出输入输出端口,这样方便复用,下面是实例项目的功能划分 2.单个模块电路的接口设计 这里以W5500以太网接口电路为例说明: 主要就分为通讯接口和电源接口:

洋洋洋的博客 3万+

DelayQueue队列详解

概述 DelayQueue是java.util.concurrent中提供的一个很有意思的类。本文将会对DelayQueue做一个介绍,然后列举应用场景。并且提供一个Delayed接口的实现和 Sample 代码。 DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期...

思考、总结、专注 4882

CS5523实战:如何用国产芯片完美替代LT8911设计MIPI转eDP电路(附原理图)

本文详细介绍了使用国产芯片CS5523完美替代LT8911进行MIPI转eDP电路设计的实战经验。文章从芯片选型、核心特性对比入手,深入剖析了电源时钟设计、MIPI/eDP接口的PCB布局与信号完整性要点,并提供了完整的原理图参考和调试排错指南,为硬件工程师实现国产化替代提供了清晰可行的方案。

yhn4567890的博客 1141

STM32CubeMX最新版下载安装全攻略(附官网访问问题解决方案)

本文提供了STM32CubeMX最新版的详细下载与安装指南,并针对国内开发者常见的官网访问缓慢、注册验证邮件延迟等问题,给出了实用的解决方案和备选策略。通过清晰的步骤解析,帮助用户快速部署这一强大的STM32图形化配置工具,从而高效启动嵌入式开发项目。

weixin_28256233的博客 196

延迟队列DelayQueue浅析

DelayQueue简介 1.DelayQueue是一个无界阻塞队列,队列内部使用PriorityQueue来实现。要添加进去的元素必须实现Delayed接口的类对象,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从中提取元素; 2.该队列头部是延迟期满后保存时间最长的Delayed元素; 3.如果延迟都没有期满,则队列没有头部,并且poll将返回null; 4.当...

水逆呱的博客 962

一文吃透 ABAP CDS SELECT:语法全景、关联与去重、可运行示例与实战心法

摘要: 本文深入解析ABAP中CDS视图的SELECT语法,重点介绍两种等价写法、关联与路径表达式优化、DISTINCT去重逻辑及WHERE/GROUP BY/HAVING/UNION等子句的应用。通过可运行的业务伙伴角色去重、关联路径查询及多角色筛选示例,演示如何将CDS特性转化为项目实践。强调关联复用性优于传统JOIN,并指出DISTINCT的权限边界与误用场景,帮助开发者从行式思维转向图式数据建模。(150字) 关键词: ABAP CDS、SELECT语法、路径表达式、DISTINCT去重、关联复用

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 395

Java 延迟队列 DelayQueue 的原理

一、什么是延迟队列(DelayQueue)? DelayQueue 是 Java 并发包 java.util.concurrent 下的一个 Class,其官方定义如下所示。 /** * An unbounded {@linkplain BlockingQueue blocking queue} of * {@code Delayed} elements, in which an element can only be taken * when it

长安明月的博客 1万+

【系统分析师】2009年真题:综合知识-答案及详解

在某银行业务的用例模型中,“取款”用例需要等到“存款”用例执行之后才能执行,两个用例之间的关系属于(1);“取款”和“存款”两个用例中都需 要执行查询余额的功能,将查询余额提取成独立的用例,那么“取款”和“存款” 用例与“查询余额”用例之间的关系属于(2)。(1)A. 关联关系 B. 扩展关系 C. 使用关系 D. 依赖关系 (2)A. 扩展关系 B. 使用关系 C. 依赖关系 D. 继承关系【参考答案】D、A雇员类含有计算报酬的行为,利用面向对象的(3),可以使得其派生类专职 雇员类和兼职雇员类计算报酬的

数据知道的博客 1万+

第11节:GPU 线程层级:线程、线程块、网格

层级标识变量功能线程threadIdx执行单元,处理数据线程块blockIdx管理多个线程,提供共享内存网格管理多个 blockGPU 编程中,合理安排线程层级,是提升性能的关键。

yumeiguo的博客 212

DelayQueue延迟队列

DelayQueue是 JUC 包(为我们提供的延迟队列,用于实现延时任务比如订单下单 15 分钟未支付直接取消。它是的一种,底层是一个基于实现的一个无界队列,是线程安全的。DelayQueue中存放的元素必须实现Delayed接口,并且需要重写getDelay()方法(计算是否到期)。默认情况下,DelayQueue会按照到期时间升序编排任务。只有当元素过期时(getDelay()方法返回值小于等于 0),才能从队列中取出。

m0_62943934的博客 2329

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Java延迟队列——DelayQueue

DelayQueue是一个无界的BlockingQueue,是线程安全的(无界指的是队列的元素数量不存在上限,队列的容量会随着元素数量的增加而扩容,阻塞队列指的是当队列内元素数量为0的时候,试图从队列内获取元素的线程将被阻塞或者抛出异常)以上是阻塞队列的特点,而延迟队列还拥有自己如下的特点:DelayQueue中存入的必须是实现了Delayed接口的对象。

qq_25046827的博客 8915

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

无界延迟队列DelayQueue

DelayQueue是一个无界的,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。

weixin_41987908的博客 804

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

延迟队列DelayQueue研究

1.什么是DelayQueue     DelayQueue 是 Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时,将发生到期。即...

Evankaka的专栏 3562

Spring延迟队列DelayQueue

DelayQueue 中存放的元素必须实现 Delayed 接口,并且需要重写 getDelay()方法(计算是否到期)。它是 BlockingQueue 的一种,底层是一个基于 PriorityQueue 实现的一个无界队列,是线程安全的。此处做一个死循环线程,轮询检查拉取队列数据。

优快云博客技术,java高级工程师,业务层架构师,高级管理认证,毕业设计可以联系我 1118

java DelayQueue 延时队列

java DelayQueue延时队列

qq_42583549的博客 2787

Java DelayQueue 延迟队列

DelayQueue是 Java 并发包()中的一个无界阻塞队列,用于存储实现了Delayed接口的元素。队列中的元素只有在达到指定的延迟时间后才能被获取。DelayQueue是基于优先级队列的小顶堆实现的阻塞队列。元素需实现Delayed接口,getDelay()返回剩余时间。适用于延迟任务调度、缓存过期管理、订单超时等场景。主要优点是线程安全、自动延迟控制,缺点是无法主动获取未到期元素。可用 ScheduledThreadPoolExecutor。

宋发元 2011

阻塞队列之DelayQueue

DelayQueue 是一个支持延时获取元素的阻塞队列,内部使用优先队列 PriorityQueue 存储元素,且元素必须实现 Delayed 接口。元素在创建时可以指定延迟时间,只有在延迟期满时才能从队列中提取。队列中的元素不是按照先进先出的顺序,而是根据延迟时间排序,延迟最短的任务会优先执行。DelayQueue 通过 ReentrantLock 和 Condition 实现线程安全,并通过 leader 线程优化等待机制。put 和 offer 方法用于添加元素,take 方法用于获取并移除延迟期满的

不愿言弃-念何的博客 969

DelayQueue队列研究

<br /> <br />1.DelayQueue<br />是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。<br /> <br /> <br /> <br />2.Delayed<br /> <br />一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。<br />此接口的实现必须定义一个 compareTo 方法,该方法提供

1万+

【阻塞队列】- DelayQueue 的原理

上一篇文章已经介绍了 LinkedBlockingQueue,这篇文章就来介绍下 DelayQueue。【阻塞队列】- 生产者和消费者模式【阻塞队列】- ArrayBlockingQueue 的原理【阻塞队列】- ArrayBlockingQueue 的原理-迭代器【阻塞队列】- LinkedBlockingQueue 的原理【阻塞队列前置知识】- Spliterator 接口【阻塞队列】- LinkedBlockingQueue - LBQSpliterator。

Jwasx的博客 1511
上一篇: 队列总结(五)LinkedBlockingQueue
下一篇: 队列总结(八)LinkedTransferQueue
MrMoving
博客等级 码龄10年 54粉丝 145原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值