深入了解延迟队列 DelayQueue

news2024/11/19 16:48:40

1. 前言

前面我们了解了基于数组,链表实现的阻塞队列,以及优先级队列。今天我们来了解下基于优先级队列的延迟队列,而且今天的内容很核心哦。 大家快搬好小板凳做好,听我慢慢分析

2. 简单实例

  • Task 类
public class Task implements Delayed {
    private String name;
    private Long time;

    public Task(String name, Long delay) {
        this.name = name;
        this.time = System.currentTimeMillis() + delay;
    }

    @Override
    public String toString() {
        return "Task{" +
                "name='" + name + '\'' +
                ", time=" + time +
                '}';
    }

    public Task() {}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getTime() {
        return time;
    }

    public void setTime(Long time) {
        this.time = time;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(time - System.currentTimeMillis(), TimeUnit.NANOSECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return (int) (this.time - ((Task)o).getTime());
    }
}
  • 延迟队列使用实例
public class T04_Queue_Test04 {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Delayed> delayeds = new DelayQueue<>();
        Task a = new Task("A", 1000l);
        Task b = new Task("B", 500l);
        Task c = new Task("C", 1200l);
        Task d = new Task("D", 300l);

        delayeds.put(a);
        delayeds.put(b);
        delayeds.put(c);
        delayeds.put(d);

        System.out.println(delayeds.take()); // D
        System.out.println(delayeds.take()); // B
        System.out.println(delayeds.take()); // A
        System.out.println(delayeds.take()); // C
    }
}

3. 核心实现

3.1 构造方法

在这里插入图片描述

3.1.1 类

在这里插入图片描述

通过上述类本身我们可以知道,类DelayQueue 的元素必须实现Delayed 才能被添加成为元素,详细的内容可以看上述实例中Task

3.1.1 构造基本属性

// 定义的锁
private final transient ReentrantLock lock = new ReentrantLock();
// 是优先级队列 因为延迟队列是基于优先级队列实现的
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 表示等待堆顶的元素
private Thread leader = null;
// 表示挂起线程的Condition
private final Condition available = lock.newCondition();

3.2 生产者方法

3.2.1 add

public boolean add(E e) {
    // 函数内部 本质调用了offer 方法
    return offer(e);
}

3.2.2 offer

// 表示添加元素的方法
public boolean offer(E e) {
    // 表示获取锁实例
    final ReentrantLock lock = this.lock;
    // 开始上锁
    lock.lock();
    try {
        // 优先级队列 添加元素
        q.offer(e);
        // 如果堆顶的元素 === 刚才添加的元素
        if (q.peek() == e) {
            leader = null;
            // 唤醒消费者 有可能之前队列中没有元素,添加元素后,消费者就可以消费了
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

3.2.3 有参offer

public boolean offer(E e, long timeout, TimeUnit unit) {
    // 因为本身延迟队列 基于 优先级队列实现的。 所以也可以理解为无界队列。 所以不会出现队列满的时候 会将线程挂起
    return offer(e);
}

3.2.4 put

public void put(E e) {
    // 一句话 本质也是基于offer实现的
    offer(e);
}

3.3 消费者方法

3.3.1 remove

public E remove() {
    // 是基于poll来删除元素
    E x = poll();
    // 如果元素不为null的话 直接返回元素
    if (x != null)
        return x;
    else
        // 反之都会抛出异常
        throw new NoSuchElementException();
}

3.3.2 poll

public E poll() {
    // 表示获取锁实例
    final ReentrantLock lock = this.lock;
    // 开始上锁
    lock.lock();
    try {
        // 获取堆顶元素 但是不会剔除元素,只是获取
        E first = q.peek();
        // 如果堆顶元素 为null || 如果> 0 的话 因为不需要等待 所以直接返回null
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            // 延迟时间到期了
            return q.poll();
    } finally {
        lock.unlock();
    }
}

3.3.3 有参poll

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    // 统一转换时间  转换纳秒
    long nanos = unit.toNanos(timeout);
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    // 加锁 可被打断锁
    lock.lockInterruptibly();
    try {
        // 一个死循环
        for (;;) {
            // 获取堆顶 元素
            E first = q.peek();
            // 如果堆顶元素是null 说明本身队列中没有元素
            if (first == null) {
                // 等待时间到了 直接返回null
                if (nanos <= 0)
                    return null;
                else
                    // 线程挂起 等待执行
                    nanos = available.awaitNanos(nanos);
            } else {
                // 如果执行到此处的话 说明堆顶是有元素的
                // 获取元素的延迟到期时间
                long delay = first.getDelay(NANOSECONDS);
                // 如果时间 <= 0 的话,直接返回堆顶元素
                if (delay <= 0)
                    return q.poll();
                // 如果到这的话 延迟时间未到 && 等待时间到了 直接发返回null
                if (nanos <= 0)
                    return null;
                first = null;
                // 等待时间 < 延迟时间
                if (nanos < delay || leader != null)
                    nanos = available.awaitNanos(nanos);
                else {
                    // 获取当前线程
                    Thread thisThread = Thread.currentThread();
                    // 设置当前线程设置为leader  马上要执行的线程
                    leader = thisThread;
                    try {
                        // 挂起
                        long timeLeft = available.awaitNanos(delay);
                        nanos -= delay - timeLeft;
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

3.3.4 take

此时take方法跟 有参poll方法类似,只不过不需要判断等待时间,只有一个延迟时间而已。

4. 总结

延迟队列核心:通过优先级队列来判断延迟时间大小,将延迟小的元素会放到堆顶。所以添加顺序 不一定等于 输出顺序。跟延迟的时间的大小有很大关系。好了,就分析到这里了,不敢说一一明白但是大体的源码意思是透彻了。如果大家有什么新的看法,可以通过评论区告诉我哦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163301.html

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

相关文章

数据结构(字符串)

字符串简称串&#xff0c;由零个或多个字符组成的有限序列&#xff0c;一般记为s&#xff1d;“a0 a1a2…an-1”,&#xff08;n≥0&#xff09;。其中s称作串名&#xff0c;用双引号括起来的字符序列是串的值。字符ai&#xff08;0≤i≤n-1&#xff09;可以是字母、数字或其它字…

开发第三天(Day 03)

首先对ipl.nas进行修改&#xff1a; ; haribote-ipl ; TAB4ORG 0x7c00 ; 这个程序被读入哪里; 以下是标准FAT12格式软盘的描述JMP entryDB 0x90DB "HARIBOTE" ; 可以自由地写引导扇区的名字 (8字节)DW 512 ; 1扇区…

【动态内存管理】-关于动态内存你只知道四个函数是不够的,这里还有题目教你怎么正确使用函数,还不进来看看??

&#x1f387;作者&#xff1a;小树苗渴望变成参天大树 &#x1f4a6;作者宣言&#xff1a;认真写好每一篇博客 &#x1f4a2; 作者gitee&#xff1a;link 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; &#x1f38a;动态内存管理&…

adrp 命令为何能获取符号的地址

疑问所在 在linux 启动时&#xff0c;在如下位置会将bootloader 传入的x0 - x3 参数保存到boot_args[] 变量中。代码如下&#xff1a; /* …

神经网络基础部件-损失函数详解

一&#xff0c;损失函数概述 大多数深度学习算法都会涉及某种形式的优化&#xff0c;所谓优化指的是改变 xxx 以最小化或最大化某个函数 f(x)f(x)f(x) 的任务&#xff0c;我们通常以最小化 f(x)f(x)f(x) 指代大多数最优化问题。 在机器学习中&#xff0c;损失函数是代价函数的…

Spring Security 多过滤链的使用

一、背景 在我们实际的开发过程中&#xff0c;有些时候可能存在这么一些情况&#xff0c;某些api 比如&#xff1a; /api/** 这些是给App端使用的&#xff0c;数据的返回都是以JSON的格式返回&#xff0c;且这些API的认证方式都是使用的TOKEN进行认证。而除了 /api/** 这些API…

Qt扫盲-QTextEdit理论总结

QTextEdit理论总结一、概述二、用途一&#xff1a;富文本阅读器1. 用法2. 快捷键绑定三、用途二&#xff1a;编辑器1. 用法2. 拖拽3. 快捷键绑定四、常用功能五、信号一、概述 QTextEdit是一个先进的所见即所得的 富文本 查看器/编辑器&#xff0c;支持使用 html 风格的标签或…

【QT】.pro 文件

&#x1f33f;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;QT开发 .pro 文件是什么 .pro就是工程文件(project)&#xff0c;它是qmake自动生成的用于生产makefile的配置文件。类似于VS中的.sln 和vsproj文件 默认情况下&#xff0c;每个 Qt 项目都包含一个后缀名为…

Huffman编码实现文件的压缩和解压缩

一个项目&#xff0c;不过处理起来也比较麻烦&#xff0c;配套可以和文件传输放一起 前提知识&#xff1a; 哈夫曼树和哈夫曼编码的概念和构建 1&#xff1a;n个数构成的哈夫曼树一共有2*n-1个结点>8 -> 15 2&#xff1a;数字越大的数离根节点越近&#xff0c;越小的数离…

无线耳机跑步会不会掉、最适合跑步用的耳机排名

现在&#xff0c;喜欢运动的人越来越多了。大家都有体会&#xff0c;多数运动是相对枯燥的&#xff0c;在运动时听听音乐&#xff0c;那是多么惬意的事情啊。为此&#xff0c;体验过多款耳机&#xff0c;但令我很满意的甚少。相信不少喜欢运动的朋友都有着跟我一样的烦恼吧&…

【Java基础知识3】Java注释:单行、多行、文档注释(如何通过 javadoc 命令生成代码文档、如何在IEDA配置自动为所有的类都添加创建者和创建日期)

本文已收录专栏 &#x1f332;《Java进阶之路》&#x1f332; 目录 本文已收录专栏 &#x1f332;《Java进阶之路》&#x1f332; &#x1f350;01、单行注释 &#x1f350;02、多行注释 &#x1f350;03、文档注释 &#x1f350;04、文档注释的注意事项 &#x1f350;05、注释…

ceres学习笔记(二)

继续关于ceres官方doc里教程的学习&#xff0c;对于powells function的学习。 一、powells function 鲍威尔法&#xff0c;严格来说是鲍威尔共轭方向法&#xff0c;是迈克尔J.D.鲍威尔提出的一种求解函数局部最小值的算法。该函数不能是可微分的&#xff0c;并且不会导出衍生函…

spring用注解读取与获取对象

前言 上一篇博客简单的介绍了spring的功能与使用&#xff0c;可以看到我们创建一个对象&#xff0c;就需要在xml中存储一个bean对象&#xff0c;这种操作非常的繁琐&#xff0c;因此spring发明了使用注解来快捷存储bean对象 配置工作 我们在xml文件中写下面的代码片段 <…

基于风光储能和需求响应的微电网日前经济调度(Python代码实现)【0】

目录 0 引言 1 计及风光储能和需求响应的微电网日前经济调度模型 1.1风光储能需求响应都不参与的模型 1.2风光参与的模型 1.3风光和储能参与模型 1.4 风光和需求响应参与模型 1.5 风光储能和需求响应都参与模型 2 需求侧响应评价 2.1 负载率 2.2 可再生能源消纳率 …

Win10PE_V2.0Nvme网络版.iso 支持Nvme硬盘免费下载无需积分

Win10PE_V2.0Nvme网络版.iso 支持Nvme硬盘免费下载无需积分 V1.0版本发布 2022年1月19日 内置常用PE工具&#xff0c;7-Zip、EasyImageX_x64、XorBoot Uefi修复、NT6修复、Ghost、CGI、Google浏览器、PENetwork、RegWorkshop、迅雷迷你版、、BOOTICEx64、windows安装器、XP安…

路径计数2

路径计数2 题目描述 一个NNN \times NNN的网格&#xff0c;你一开始在(1,1)(1,1)(1,1)&#xff0c;即左上角。每次只能移动到下方相邻的格子或者右方相邻的格子&#xff0c;问到达(N,N)(N,N)(N,N)&#xff0c;即右下角有多少种方法。 但是这个问题太简单了&#xff0c;所以现…

MySQL 数据同步 Elasticsearch 的技术方案选型

文章目录1.同步双写2.异步双写3.定时任务4.数据订阅1.同步双写 优点&#xff1a;实现简单缺点&#xff1a; 业务耦合&#xff0c;商品的管理中耦合大量数据同步代码 影响性能&#xff0c;写入两个存储&#xff0c;响应时间变长 不便扩展&#xff1a;搜索可能有一些个性化需求&…

jvm学习的核心(三)---运行时数据区详解(1)

图片等相关信息来源于&#xff1a;尚硅谷宋红康JVM全套教程 1.程序计数器 程序计数器又叫pc寄存器&#xff0c;中文有两个名字 我们可以反编译字节码文件查看方法中操作指令对应的指令地址 javap -v "对应的class文件"为什么要用pc寄存器&#xff0c;pc寄存器有什…

13、Javaweb_Filter登陆验证动态代理过滤敏感词Listener

Filter&#xff1a;过滤器 1. 概念&#xff1a; * 生活中的过滤器&#xff1a;净水器,空气净化器&#xff0c;土匪、 * web中的过滤器&#xff1a;当访问服务器的资源时&#xff0c;过滤器可以将请求拦截下来&#xff0c;完成一些特殊的功能。 * 过滤器的作用&…

深入理解计算机系统_可执行目标文件和可重定位目标文件的3个区别

这篇笔记对比一下可执行目标文件和可执行目标的3个区别。下图分别是可重定位目标文件和可执行目标文件各段结构。 1.1 可执行目标文件和可重定位目标文件的3个区别 区别1&#xff1a;可执行目标文件的rel.text和.rel.data消失了 链接器将.o中.text和.data节整合到一起时&a…