DelayQueue源码分析

news2024/10/6 0:18:34

底层

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

DelayQueue 常见使用场景示例

我们这里希望任务可以按照我们预期的时间执行,例如提交 3 个任务,分别要求 1s、2s、3s 后执行,即使是乱序添加,1s 后要求 1s 执行的任务会准时执行。
对此我们可以使用 DelayQueue 来实现,所以我们首先需要继承 Delayed 实现 DelayedTask,实现 getDelay 方法以及优先级比较 compareTo。

/**
 * 延迟任务
 */
public class DelayedTask implements Delayed {
    /**
     * 任务到期时间
     */
    private long executeTime;
    /**
     * 任务
     */
    private Runnable task;

    public DelayedTask(long delay, Runnable task) {
        this.executeTime = System.currentTimeMillis() + delay;
        this.task = task;
    }

    /**
     * 查看当前任务还有多久到期
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 延迟队列需要到期时间升序入队,所以我们需要实现compareTo进行到期时间比较
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        return Long.compare(this.executeTime, ((DelayedTask) o).executeTime);
    }

    public void execute() {
        task.run();
    }
}

完成任务的封装之后,使用就很简单了,设置好多久到期然后将任务提交到延迟队列中即可。

// 创建延迟队列,并添加任务
DelayQueue < DelayedTask > delayQueue = new DelayQueue < > ();

//分别添加1s、2s、3s到期的任务
delayQueue.add(new DelayedTask(2000, () -> System.out.println("Task 2")));
delayQueue.add(new DelayedTask(1000, () -> System.out.println("Task 1")));
delayQueue.add(new DelayedTask(3000, () -> System.out.println("Task 3")));

// 取出任务并执行
while (!delayQueue.isEmpty()) {
  //阻塞获取最先到期的任务
  DelayedTask task = delayQueue.take();
  if (task != null) {
    task.execute();
  }
}

结构

在这里插入图片描述

四个核心成员变量

//可重入锁,实现线程安全的关键
private final transient ReentrantLock lock = new ReentrantLock();
//延迟队列底层存储数据的集合,确保元素按照到期时间升序排列
private final PriorityQueue<E> q = new PriorityQueue<E>();

//指向准备执行优先级最高的线程
private Thread leader = null;
//实现多线程之间等待唤醒的交互
private final Condition available = lock.newCondition();

lock : 我们都知道 DelayQueue 存取是线程安全的,所以为了保证存取元素时线程安全,我们就需要在存取时上锁,而 DelayQueue 就是基于 ReentrantLock 独占锁确保存取操作的线程安全。
q : 延迟队列要求元素按照到期时间进行升序排列,所以元素添加时势必需要进行优先级排序,所以 DelayQueue 底层元素的存取都是通过这个优先队列 PriorityQueue 的成员变量 q 来管理的。
leader : 延迟队列的任务只有到期之后才会执行,对于没有到期的任务只有等待,为了确保优先级最高的任务到期后可以即刻被执行,设计者就用 leader 来管理延迟任务,只有 leader 所指向的线程才具备定时等待任务到期执行的权限,而其他那些优先级低的任务只能无限期等待,直到 leader 线程执行完手头的延迟任务后唤醒它。
available : 上文讲述 leader 线程时提到的等待唤醒操作的交互就是通过 available 实现的,假如线程 1 尝试在空的 DelayQueue 获取任务时,available 就会将其放入等待队列中。直到有一个线程添加一个延迟任务后通过 available 的 signal 方法将其唤醒

构造方法

相较于其他的并发容器,延迟队列的构造方法比较简单,它只有两个构造方法,因为所有成员变量在类加载时都已经初始完成了,所以默认构造方法什么也没做。

添加元素

public boolean offer(E e) {
    //尝试获取lock
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //如果上锁成功,则调q的offer方法将元素存放到优先队列中
        q.offer(e);
        //调用peek方法看看当前队首元素是否就是本次入队的元素,如果是则说明当前这个元素是即将到期的任务(即优先级最高的元素)
        if (q.peek() == e) {
            //将leader设置为空,通知调用取元素方法而阻塞的线程来争抢这个任务
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        //上述步骤执行完成,释放lock
        lock.unlock();
    }
}

  • 尝试获取 lock 。
  • 如果上锁成功,则调 q 的 offer 方法将元素存放到优先队列中。
  • 调用 peek 方法看看当前队首元素是否就是本次入队的元素,如果是则说明当前这个元素是即将到期的任务(即优先级最高的元素),于是将 leader 设置为空,通知因为队列为空时调用 take 等方法导致阻塞的线程来争抢元素。
  • 上述步骤执行完成,释放 lock。
  • 返回 true。

获取元素

DelayQueue 中获取元素的方式分为阻塞式和非阻塞式

public E take() throws InterruptedException {
    // 尝试获取可重入锁,将底层AQS的state设置为1,并设置为独占锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            //查看队列第一个元素
            E first = q.peek();
            //若为空,则将当前线程放入ConditionObject的等待队列中,并将底层AQS的state设置为0,表示释放锁并进入无限期等待
            if (first == null)
                available.await();
            else {
                //若元素不为空,则查看当前元素多久到期
                long delay = first.getDelay(NANOSECONDS);
                //如果小于0则说明已到期直接返回出去
                if (delay <= 0)
                    return q.poll();
                //如果大于0则说明任务还没到期,首先需要释放对这个元素的引用
                first = null; // don't retain ref while waiting
                //判断leader是否为空,如果不为空,则说明正有线程作为leader并等待一个任务到期,则当前线程进入无限期等待
                if (leader != null)
                    available.await();
                else {
                    //反之将我们的线程成为leader
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        //并进入有限期等待
                        available.awaitNanos(delay);
                    } finally {
                        //等待任务到期时,释放leader引用,进入下一次循环将任务return出去
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        //收尾逻辑:如果leader不为空且q有元素,则说明有任务没人认领,直接发起通知唤醒因为锁被当前消费者持有而导致阻塞的生产者(即调用put、add、offer的线程)
        if (leader == null && q.peek() != null)
            available.signal();
        //释放锁
        lock.unlock();
    }
}

public E poll() {
    //尝试获取可重入锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //查看队列第一个元素,判断元素是否为空
        E first = q.peek();

        //若元素为空,或者元素未到期,则直接返回空
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            //若元素不为空且到期了,直接调用poll返回出去
            return q.poll();
    } finally {
        //释放可重入锁lock
        lock.unlock();
    }
}

查看元素

public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return q.peek();
    } finally {
        lock.unlock();
    }
}

DelayQueue 的实现原理是什么?

DelayQueue 底层是使用优先队列 PriorityQueue 来存储元素,而 PriorityQueue 采用二叉小顶堆的思想确保值小的元素排在最前面,这就使得 DelayQueue 对于延迟任务优先级的管理就变得十分方便了。同时 DelayQueue 为了保证线程安全还用到了可重入锁 ReentrantLock,确保单位时间内只有一个线程可以操作延迟队列。最后,为了实现多线程之间等待和唤醒的交互效率,DelayQueue 还用到了 Condition,通过 Condition 的 await 和 signal 方法完成多线程之间的等待唤醒。

DelayQueue 的实现是否线程安全?

DelayQueue 的实现是线程安全的,它通过 ReentrantLock 实现了互斥访问和 Condition 实现了线程间的等待和唤醒操作,可以保证多线程环境下的安全性和可靠性。

DelayQueue 的使用场景有哪些?

DelayQueue 通常用于实现定时任务调度和缓存过期删除等场景。在定时任务调度中,需要将需要执行的任务封装成延迟任务对象,并将其添加到 DelayQueue 中,DelayQueue 会自动按照剩余延迟时间进行升序排序(默认情况),以保证任务能够按照时间先后顺序执行。对于缓存过期这个场景而言,在数据被缓存到内存之后,我们可以将缓存的 key 封装成一个延迟的删除任务,并将其添加到 DelayQueue 中,当数据过期时,拿到这个任务的 key,将这个 key 从内存中移除

DelayQueue 中 Delayed 接口的作用是什么?

Delayed 接口定义了元素的剩余延迟时间(getDelay)和元素之间的比较规则(该接口继承了 Comparable 接口)。若希望元素能够存放到 DelayQueue 中,就必须实现 Delayed 接口的 getDelay() 方法和 compareTo() 方法,否则 DelayQueue 无法得知当前任务剩余时长和任务优先级的比较。

DelayQueue 和 Timer/TimerTask 的区别是什么?

DelayQueue 和 Timer/TimerTask 都可以用于实现定时任务调度,但是它们的实现方式不同。DelayQueue 是基于优先级队列和堆排序算法实现的,可以实现多个任务按照时间先后顺序执行;而 Timer/TimerTask 是基于单线程实现的,只能按照任务的执行顺序依次执行,如果某个任务执行时间过长,会影响其他任务的执行。另外,DelayQueue 还支持动态添加和移除任务,而 Timer/TimerTask 只能在创建时指定任务。

作者声明

如有问题,欢迎指正!

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

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

相关文章

UG\NX二次开发 二维向量相加

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 简介: UG\NX二次开发 二维向量相加 效果: 代码: #include "me.hpp"void doIt() {const double vec1[2] = { 1.0,2.0 };const double vec2[2] = { 2.0,2.…

高效成绩查询系统助力,让学校管理事半功倍

各位老师们&#xff0c;大家好&#xff01;作为教育工作者&#xff0c;我们都了解成绩查询在学校管理中的重要性。然而&#xff0c;传统的查询方式往往繁琐耗时&#xff0c;给我们带来了不少困扰。因此&#xff0c;今天我将向大家介绍一个极其便捷的查询工具&#xff0c;能够帮…

VR云游:让游客足不出户享受旅行的乐趣

随着人们的生活水平不断提高&#xff0c;对于旅行的需求也在日益增长&#xff0c;既要玩的舒心&#xff0c;也要享受的舒服&#xff0c;因此VR全景云游就成为了一种新型的旅行方式&#xff0c;人们足不出户就可以沉浸式游览各地自然风光与名胜古迹。 VR云游景区是一种全新的旅游…

反渗透,sql注入漏洞扫描工具

工具一:zap 下载地址&#xff1a;ZAP GitHub OWASP Zed 攻击代理&#xff08;ZAP&#xff09;是世界上最受欢迎的免费安全审计工具之一&#xff0c;由数百名国际志愿者积极维护。它可以帮助你在开发和测试应用程序时自动查找 Web 应用程序中的安全漏洞。可以说 ZAP 是一个中…

百货店失去核心竞争力了吗?全靠超市即时零售撑起

近年来&#xff0c;传统百货不景气&#xff0c;不过&#xff0c;从今年上半年的情况看&#xff0c;似乎有些好转。 然而&#xff0c;实际情况并不如预期那么好&#xff0c;有专家认为&#xff1a;“百货上市公司业绩和去年相比增长是正常的。实际上&#xff0c;百货业态增长的…

陪诊系统|陪诊软件为用户提供多元化的医疗服务

陪诊小程序作为一种人性化的医疗工具&#xff0c;具备多种功能。首先&#xff0c;它可以预约挂号。患者只需通过简单的操作&#xff0c;即可轻松选择合适的医生和挂号时间&#xff0c;免去了排队等待的繁琐过程。其次&#xff0c;陪诊小程序为患者提供了在线问诊的渠道。通过平…

企业怎么管理自己固定资产的

在当今的商业世界中&#xff0c;企业的固定资产是其生存和发展的基础。然而&#xff0c;仅仅关注这些有形的资产&#xff0c;如土地、建筑和设备&#xff0c;可能会忽视企业最宝贵的资产——人力资源和知识。因此&#xff0c;成功的企业不仅需要管理好自己的有形资产&#xff0…

小白学Unity03-太空漫游游戏脚本,控制飞船移动旋转

首先搭建好太阳系以及飞机的场景 需要用到3个脚本 1.控制飞机移动旋转 2.控制摄像机LookAt朝向飞机和差值平滑跟踪飞机 3.控制各个星球自转以及围绕太阳旋转&#xff08;rotate()和RotateAround()&#xff09; 1.控制飞机移动旋转的脚本 using System.Collections; using…

Hypotenuse AI:AI文本生成工具

【产品介绍】​ 名称 Hypotenuse 成立时间​ 2020年 具体描述 Hypotenuse AI是一种文本生成工具&#xff0c;它使用人工智能来帮助您生成相关内容。如果您从事数字营销、联盟营销、文案撰写或博客写作&#xff0c;您就会了解创建顶级内容的重要性。Hypotenus…

华为HCIA(四)

链路聚合可以负载分担&#xff0c;增加带宽&#xff0c;提高可靠性 Eth-trunk的传输速率和成员端口数量喝带宽有关 路由器分割广播域&#xff0c;交换机分割冲突域 指定端口&#xff1a;DP;根端口&#xff1a;RP;阻塞端口&#xff1a;AP 如果目的MAC不在交换机MAC中&…

PHP8中字符串与数组的转换-PHP8知识详解

在php8中使用explode()函数和implode()函数实现字符串和数组之间的转换。 1、使用explode()函数把字符串按照一定的规则拆分为数组中的元素&#xff0c;并且形成数组。 使用explode()函数把字符串转换数组&#xff0c;示范代码&#xff1a; <?php $string "html,cs…

Java操作Influxdb2.x

本片文章不讲怎么安装&#xff0c;只讲安装后如何用JAVA代码操作库表 1.创建数据库2.为bucket添加TELEGRAF配置3.TELEGRAF配置参数说明4.配置数据库的访问权限API TOKENS5.JAVA代码操作库表5.1 yaml5.2 pom依赖5.3 config5.4 controller5.5 查询方法、结果集提取方法 1.创建数据…

Dataworks实现接口调用

RestAPI调用 功能&#xff1a;实现Restful风格的API调用 步骤一&#xff1a;配置RestAPI数据源&#xff0c;在url中填Restful风格的url&#xff0c;若是需要账号密码登录等可以切换“验证方法” 步骤二&#xff1a;在离线同步中创建离线同步任务&#xff0c;数据来源选择配置…

windows环境变量滥用维权/提权

本文转载于&#xff1a;https://bbs.zkaq.cn/t/31090.html 0x01 前提 通过滥用系统的路径搜索机制来欺骗高权限用户执行看似合法的系统二进制文件&#xff0c;实际上是恶意的代码或程序&#xff0c;从而导致升权限并执行恶意操作。 攻击的关键前提&#xff1a; 路径搜索顺序&am…

联合华为“围剿”苹果,他笑得憋都憋不住

作者&#xff1a;狗蛋 螳螂观察快评&#xff1a;联合华为围剿苹果&#xff0c;荣耀总裁赵明笑得憋都憋不住&#xff0c;离开华为的荣耀过得怎么样了&#xff1f;#华为 #荣耀发布会 #赵明 #苹果开售 #中国芯片 华为5g手机王者归来&#xff0c;苹果慌了&#xff0c;其他友商也慌…

【论文记录】Boosting Detection in Crowd Analysis via Underutilized Output Features

Boosting Detection in Crowd Analysis via Underutilized Output Features Abstract Crowd Hat使用一种混合的2D-1D压缩技术进行细化空间特征与获取特定人群信息的空间和数量分布。进一步的&#xff0c;Crowd Hat采用自适应区域的NMS阈值与一个解耦然后对齐的范式来解决基于…

C++【视频笔记个人思考_Wang】

时间进度C是什么&#xff1f;多态什么是多态&#xff1f;生活中的多态C中的多态 赋值兼容赋值兼容规则实现安全转换 时间进度 Day101 ok Day804 ok Day805 ok C是什么&#xff1f; C大部分包含C语言。 C完全兼容C语言。 C在C语言的基础上添加&#xff1a;封装、继承、多态…

代码混淆和加固,保障应用程序的安全性

摘要&#xff1a;本文将详细介绍iOS技术博主在保护应用程序代码安全方面的两种重要方式&#xff1a;代码混淆和代码加固。通过代码混淆和加固&#xff0c;博主可以有效防止他人对应用程序进行逆向工程和篡改&#xff0c;提高应用程序的安全性。 引言&#xff1a;作为iOS技术博…

嵌入式Linux驱动开发(I2C专题)(六)

完善虚拟的I2C_Adapter驱动并模拟EEPROM 参考资料&#xff1a; Linux内核文档: Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txtLinux-5.4\Documentation\devicetree\bindings\i2c\i2c-gpio.yaml Linux内核驱动程序&#xff1a;使用GPIO模拟I2C Linux-4.9.…

【洁洁送书第七期】现在学 Java 找工作还有优势吗

java 现在学 Java 找工作还有优势吗&#xff1f;活力四射的 JavaTIOBE 编程语言排行榜从零开始学会 JavaJava 语言运行过程基础知识进阶知识高级知识talk is cheap, show me the code结语 文末赠书 现在学 Java 找工作还有优势吗&#xff1f; 在某乎上可以看到大家对此问题的…