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

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

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

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
来实例化延迟队列。


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

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...