百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

一文读懂,Java内置的延迟队列DelayQueue,原理及使用方法

bigegpt 2025-03-19 10:40 6 浏览

Java的延迟队列(DelayQueue)是一种带有延迟时间的阻塞队列,最初在JDK1.5中引入。它允许我们向队列中添加具有延迟时间的元素,并在元素到期后从队列中获取这些元素。

一、实现原理

Java 延迟队列的实现基于 priority queue (优先级队列),队列中的元素根据到期时间排序。队列头部是最先到期的元素,每个元素都可以有不同的到期时间。DelayQueue 内部使用了堆排序算法,因此可以快速高效地查找、插入和删除元素。即使队列中的元素数量非常庞大,它的性能也不会受到影响。

当添加一个元素到队列中时,该元素会按照其到期时间插入到适当的位置,排成有序序列。当队列中的元素到达到期时间时,该元素会从队列头部被移除。

每个元素都是一个实现了
java.util.concurrent.Delayed
接口的对象,该接口定义了两个方法:

long getDelay(TimeUnit unit); // 返回该元素还需等待的时间。
int compareTo(Delayed o); // 对元素进行比较,以便于维护过期元素的顺序。

Java 延迟队列 DelayQueue 实现了 BlockingQueue 接口,因此它具备了阻塞等待的能力,当尝试取出一个元素时,如果队列中没有到期的元素,那么当前线程会进入阻塞状态,直到有一个元素过期或者插入一个过期的元素,唤醒当前线程进行取出操作。

public class DelayQueue extends AbstractQueue
    implements BlockingQueue {

    private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue q = new PriorityQueue();
    ......
    
}

二、代码示例

定义了一个名为 DelayedElement 的类,它包含一个延迟时间和一个字符串 data。在构造函数中,我们计算出需要等待的时间,并将延迟时间设置为当前时间加上该时间。

实现了 Delayed 接口之后,我们需要实现 getDelaycompareTo 方法。 getDelay 方法返回元素还需要等待的时间, compareTo 方法用于比较两个元素的优先级,其中优先级由剩余的延迟时间决定。

public class DelayedElement implements Delayed {
     // 元素的到期时间
    private long delayTime;
    // 元素包含的数据
    private String data; 

    /**
     * 创建一个带有延迟时间的元素对象
     * @param delayTime 延迟时间,单位为毫秒
     * @param data 包含的数据
     */
    public DelayedElement(long delayTime, String data) {
       // 计算元素的到期时间
        this.delayTime = System.currentTimeMillis() + delayTime; 
        this.data = data;
    }

    /**
     * 获取元素的剩余延迟时间
     * @param unit 时间单位
     * @return 元素的剩余延迟时间
     */
    @Override
    public long getDelay(TimeUnit unit) {
        // 计算元素到期时间与当前时间的时间差
        long diff = delayTime - System.currentTimeMillis();
        // 将时间差转换为指定的时间单位
        return unit.convert(diff, TimeUnit.MILLISECONDS); 
    }

    /**
     * 比较两个元素的到期时间,用于优先级队列的排序
     * @param o 要比较的另一个元素
     * @return -1、0 或 1,分别代表该元素到期时间早于、等于或晚于另一个元素
     */
    @Override
    public int compareTo(Delayed o) {
        if (this.delayTime < delayedelement o.delaytime return -1 if this.delaytime> ((DelayedElement) o).delayTime) {
            return 1;
        }
        return 0;
    }

    /**
     * 返回元素的字符串表示
     * @return 元素的字符串表示
     */
    @Override
    public String toString() {
        return "DelayedElement{" +
                "delayTime=" + delayTime +
                ", data='" + data + '\'' +
                '}';
    }
}

在主函数中,首先创建一个 DelayQueue。然后,我们使用 put 方法将两个 DelayedElement 对象放入队列中。最后,我们一直从队列中取元素并输出它们,直到队列为空。

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayedQueueExample {

    public static void main(String[] args) throws InterruptedException {
        // 创建 DelayQueue
        DelayQueue queue = new DelayQueue<>();

        // 将元素放入 DelayQueue,延迟1s
        queue.put(new DelayedElement(1000, "Hello"));
        // 将元素放入 DelayQueue,延迟5s
        queue.put(new DelayedElement(5000, "World"));

        // 获取延迟元素并输出
        while (!queue.isEmpty()) {
            DelayedElement element = queue.take();
            System.out.println(element);
        }
    }
}

输出结果:

DelayedElement{delayTime=1620478571034, data='Hello'}
DelayedElement{delayTime=1620478576031, data='World'}

第一个元素的延迟时间为1000毫秒,第二个元素的延迟时间为5000毫秒。

三、使用场景举例

Java 的延迟队列可以用在很多场景中,比如任务调度器。在某个时间点执行某个特定的任务,或者在某个时间段内以指定的频率执行任务。Java 延迟队列可以轻松地实现这样的任务调度器。

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class TaskScheduler {
    private DelayQueue queue = new DelayQueue<>();

    public void schedule(Task task) {
        queue.offer(task);
    }

    public void run() {
        while (!queue.isEmpty()) {
            try {
                Task task = queue.take();
                task.run();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static class Task implements Delayed {
        private Runnable runnable;
        private long executeTime;

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

        public void run() {
            runnable.run();
        }

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

        @Override
        public int compareTo(Delayed o) {
            return Long.compare(executeTime, ((Task)o).executeTime);
        }
    }
}

我们可以在任务调度器中添加一个任务,该任务包含要执行的代码块和延迟时间。当调用 schedule 方法添加任务时,任务会按照其延迟时间插入到延迟队列中。然后,我们可以调用 run 方法来运行任务调度器并等待任务执行。

四、优缺点

优点:

  • 简单易用:Java 的 DelayQueue 工具很容易使用,不需要过多的配置和大量的代码。只需简单实现 Delayed 接口即可。
  • 高效:基于优先级队列的实现方式,DelayQueue 内部使用了堆排序算法,因此可以快速高效地查找、插入和删除元素。即使队列中的元素数量非常庞大,它的性能也不会受到影响。

缺点:

  • 不支持任务取消:一旦任务被添加到 DelayQueue 中,就无法取消或删除它,这可能会导致不必要的资源占用。
  • 对系统内存的消耗:DelayQueue 内部实现是一个优先级队列,随着任务数量的增加,队列大小会逐渐增大,这可能会占用大量的系统内存。
  • 无法保证任务执行的精确时间:由于 DelayQueue 是基于时间的延迟机制,因此任务的执行时间不能保证精确,任务的实际执行时间可能比预期要早或者要晚。

五、总结

延迟队列是一个非常有用的 Java 数据结构,它可以用于多种场景,包括缓存过期、任务超时和心跳检测等。在延迟队列中,每个元素都有一个过期时间,当元素到期时,其相关操作被执行。Java的延迟队列通过维护一个优先级队列来实现,元素以一定的优先级顺序存放在队列中。从延迟队列中获取元素时,如果元素还没有过期,将会被阻塞,直到元素到期或者被删除。在 Java 中,我们可以使用
java.util.concurrent.DelayQueue
来实例化延迟队列。


对于本文的内容,不知道你有没有什么看法,欢迎在评论区里留言。如果你对我的文章内容感兴趣,请点击关注,谢谢支持!

相关推荐

【机器学习】数据挖掘神器LightGBM详解(附代码)

来源:机器学习初学者本文约11000字,建议阅读20分钟本文为你介绍数据挖掘神器LightGBM。LightGBM是微软开发的boosting集成模型,和XGBoost一样是对GBDT...

3分钟,用DeepSeek全自动生成语音计算器,还带括号表达式!

最近,大家慢慢了解到了DeepSeek的强大功能,特别是它在编程领域也同样强大。编程零基础小白,一行代码不用写,也能全自动生成一个完整的、可运行的软件来!很多程序员一直不相信小白不写代码也能编软件!下...

python学习笔记 3.表达式

在Python中,表达式是由值、变量和运算符组成的组合。以下是一些常见的Python表达式:算术表达式:由数值和算术运算符组成的表达式,如加减乘除等。例如:5+3、7*2、10/3等。字符...

5.7 VS 8.x,为什么用户不升级MySql

一般来说为了更好的功能和性能,都需要将软件升级到最新的版本,然而在开源软件中,由于一些开发商变化或其他的问题(开源授权变化),致使人们不愿使用最新的版本,一个最典型的问题就是CentOS操作系统。还有...

大厂高频:讲一下MySQL主从复制

大家经常听说主从复制,那么主从复制的意义?能解决的问题有哪些?主从复制能解决的问题就是在我们平时开发的程序中操作数据库的时候,大多数的情况查询的操作大大超过了写的操作,也就说对数据库读取数据的压力比较...

MYSQL数据库的五大安全防护措施

以技术为基础的企业里最有价值的资产莫过于是客户或者其数据库中的产品信息了。因此,在这样的企业中,保证数据库免受外界攻击是数据库管理的重要环节。很多数据库管理员并没有实施什么数据库保护措施,只是因为觉得...

docker安装mysql

准备工作已安装Docker环境(官方安装文档)终端/命令行工具(Linux/macOS/WSL)步骤1:拉取MySQL镜像打开终端执行以下命令,拉取官方MySQL镜像(默认最新版本):d...

Zabbix监控系统系列之六:监控 mysql

zabbix监控mysql1、监控规划在创建监控项之前要尽量考虑清楚要监控什么,怎么监控,监控数据如何存储,监控数据如何展现,如何处理报警等。要进行监控的系统规划需要对Zabbix很了解,这里只是...

详解MySQL的配置文件及优化

#头条创作挑战赛#在Windows系统中,MySQL服务器启动时最先读取的是my.ini这个配置文件。在Linux系统中,配置文件为my.cnf,其路径一般为/etc/my.cnf或/etc/mysq...

Mysql 几个批处理执行脚本

学习mysql过程中,需要创建测试数据,并让多人每人一个数据库连接并进行作业检查。整合部分批处理创建数据批量创建数据库DELIMITER$CREATEPROCEDURECreateDatab...

MySQL学到什么程度?才有可以在简历上写精通

前言如今互联网行业用的最多就是MySQL,然而对于高级Web面试者,尤其对于寻找30k下工作的求职者,很多MySQL相关知识点基本都会涉及,如果面试中,你的相关知识答的模糊和不切要点,基...

mysql 主、从服务器配置“Slave_IO_Running: Connecting” 问题分析

#在进行mysql主、从服务器配置时,”SHOWSLAVESTATUS;“查看从库状态Slave_IO_Runing,出现错误:“Slave_IO_Running:Connectin...

MYSQL数据同步

java开发工程师在实际的开发经常会需要实现两台不同机器上的MySQL数据库的数据同步,要解决这个问题不难,无非就是mysql数据库的数据同步问题。但要看你是一次性的数据同步需求,还是定时数据同步,亦...

「MySQL 8」MySQL 5.7都即将停只维护了,是时候学习一波MySQL 8了

MySQL8新特性选择MySQL8的背景:MySQL5.6已经停止版本更新了,对于MySQL5.7版本,其将于2023年10月31日停止支持。后续官方将不再进行后续的代码维护。另外,...

Prometheus监控mysql

通过Prometheus监控Mysql,我们需要在Mysql端安装一个mysql-exporter,然后Prometheus通过mysql-exporter暴露的端口抓取数据。1.安装一个MYSQL配...