Java实现DelayQueue延迟队列示例代码

 更新时间:2025年07月05日 11:37:38   作者:宋发元  
Java中的DelayQueue是一个特殊的队列,它只允许在指定的延迟时间之后才能从队列中取出元素,这篇文章主要给大家介绍了关于Java实现DelayQueue延迟队列的相关资料,需要的朋友可以参考下

JavaDelayQueue延迟队列

1.DelayQueue概述

DelayQueue 是 Java 并发包(java.util.concurrent)中的一个 无界 阻塞队列,用于存储实现了 Delayed 接口的元素。队列中的元素只有在达到指定的延迟时间后才能被获取。

2.DelayQueue的底层数据结构

DelayQueue 的底层数据结构是 优先级队列(PriorityQueue),它是一个小顶堆(最小堆),根据元素的过期时间进行排序。

  • 底层采用 PriorityQueue(基于堆的实现)
  • 按照到期时间升序排列,即最早过期的元素在堆顶
  • 元素未过期时,take() 方法会阻塞
  • 支持多线程并发访问

3.DelayQueue的实现原理

  • 元素需实现 Delayed 接口,重写 getDelay() 方法,返回剩余的延迟时间

  • DelayQueue 内部维护一个 PriorityQueue<Delayed>

  • 插入元素时,按照到期时间排序,最早到期的元素位于堆顶。

  • take()
    

    方法获取堆顶元素:

    • 若到期,直接返回该元素。
    • 若未到期,线程阻塞,直到该元素可用。
    • 使用锁 + 条件变量ReentrantLock + Condition)控制并发访问。

4.DelayQueue的应用场景

DelayQueue 适用于 延迟执行、定时任务、缓存超时管理 等场景,包括:

  • 任务调度(如延迟执行任务、重试机制)
  • 定时消息队列(如 Kafka 里的延时消息)
  • 订单超时取消(未支付订单自动取消)
  • 缓存自动过期(定期清除缓存)
  • 连接超时管理(网络连接的超时处理)

5.DelayQueue的优缺点

优点

  • 高效的时间管理,自动处理过期元素
  • 线程安全,内部使用 ReentrantLock 保证并发安全
  • 无界队列,但受内存限制
  • 阻塞机制,减少 CPU 轮询

缺点

  • 不支持元素移除(除非手动遍历 remove()
  • 不能提前获取未到期元素poll() 只返回到期元素)
  • 无上限(可能导致 OOM)

6.DelayQueue的替代方案

需求替代方案
需要定时任务ScheduledThreadPoolExecutor
需要分布式延迟队列Redis ZSet(基于时间戳排序)
高吞吐延迟消息队列Kafka + 延迟插件
低延迟任务调度TimeWheel(时间轮算法,如 Netty 的 HashedWheelTimer)

7.DelayQueue使用示例

(1) 定义延迟元素

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

class DelayedTask implements Delayed {
    private final long delayTime; // 延迟时间
    private final long expireTime; // 过期时间
    private final String name;

    public DelayedTask(String name, long delay, TimeUnit unit) {
        this.name = name;
        this.delayTime = TimeUnit.MILLISECONDS.convert(delay, unit);
        this.expireTime = System.currentTimeMillis() + this.delayTime;
    }

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

    @Override
    public int compareTo(Delayed o) {
        return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
    }

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

(2) 使用DelayQueue

import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DelayQueueExample {
    public static void main(String[] args) {
        DelayQueue<DelayedTask> queue = new DelayQueue<>();
        
        queue.add(new DelayedTask("Task1", 3, TimeUnit.SECONDS));
        queue.add(new DelayedTask("Task2", 1, TimeUnit.SECONDS));
        queue.add(new DelayedTask("Task3", 5, TimeUnit.SECONDS));
        
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(() -> {
            while (!queue.isEmpty()) {
                try {
                    DelayedTask task = queue.take(); // 阻塞获取到期任务
                    System.out.println("Executing: " + task);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        
        executor.shutdown();
    }
}

(3) 运行结果

Executing: Task{name='Task2', expireTime=...}  // 1s 后执行
Executing: Task{name='Task1', expireTime=...}  // 3s 后执行
Executing: Task{name='Task3', expireTime=...}  // 5s 后执行

8. 总结

  • DelayQueue 是基于优先级队列的小顶堆实现的阻塞队列
  • 元素需实现 Delayed 接口,getDelay() 返回剩余时间。
  • 适用于 延迟任务调度、缓存过期管理、订单超时 等场景。
  • 主要优点是 线程安全、自动延迟控制,缺点是 无法主动获取未到期元素
  • 可用 ScheduledThreadPoolExecutor、Redis ZSet、Kafka 延时队列 作为替代方案。

分布式微服务架构下,能使用DelayQueue吗?

分布式微服务架构 下,不推荐直接使用 DelayQueue,主要原因如下:

1.DelayQueue的局限性

  • 单机限制DelayQueueJVM 内存队列,它运行在单个进程,无法在多个微服务实例间共享数据,不能保证高可用性和扩展性
  • 数据丢失风险:若微服务实例崩溃或重启,DelayQueue 中的任务会丢失,缺乏持久化机制。
  • 无水平扩展能力:随着流量增长,多个实例无法共享队列,容易成为瓶颈。

2. 适用于DelayQueue的场景

尽管 DelayQueue 不能直接用于分布式架构,但在单机任务调度、短时间小规模的延迟任务场景下仍然可行,例如:

  • 同一个微服务实例内的短期任务(如 1-10 秒级的延迟任务)
  • 不需要高可靠性的本地任务(如定期缓存清理)
  • 没有跨实例同步要求的任务(如本地事件延迟处理)

3. 分布式替代方案

若要在分布式微服务架构中实现可扩展、高可用的延迟任务调度,可以采用以下方案:

(1) Redis ZSet(有序集合)+ 定时轮询

  • 原理:利用 Redis 的 ZSet(有序集合),按照 score 存储任务的执行时间戳,每隔 N 毫秒 轮询一次取出到期任务执行。

  • 优势:

    • 支持 分布式部署,多个实例可共享数据
    • 持久化,即使服务重启,任务仍然存在
    • 高性能,Redis 读写性能优越
  • 示例:

    jedis.zadd("delayQueue", System.currentTimeMillis() + 5000, "order:123"); // 5s 后执行
    Set<String> tasks = jedis.zrangeByScore("delayQueue", 0, System.currentTimeMillis());
    if (!tasks.isEmpty()) {
        tasks.forEach(task -> {
            process(task); // 处理任务
            jedis.zrem("delayQueue", task); // 移除已处理任务
        });
    }
    
  • 适用场景:

    • 订单超时处理
    • 定时消息推送
    • 低吞吐的延迟任务(如秒级延迟)

(2) Kafka + 延迟队列插件

  • 原理:Kafka 通过 Kafka Streams延迟队列插件(如 Kafka Delay Message)支持延迟消费消息。
  • 适用场景:
    • 高吞吐的延迟任务
    • 可靠的分布式消息队列
  • 缺点:
    • 依赖 Kafka,适用于 需要消息队列的业务

(3) RabbitMQ/ActiveMQ TTL + 死信队列

  • 原理:RabbitMQ 支持 TTL(Time-To-Live) 设置,消息超时后自动进入 DLX(Dead Letter Exchange, 死信队列),可用 消费者监听 处理。

  • 适用场景:

    • 需要可靠消息队列
    • 需要高吞吐延迟任务
  • 示例:

    channel.queueDeclare("delayQueue", true, false, false, Map.of("x-message-ttl", 5000));
    channel.basicPublish("", "delayQueue", MessageProperties.PERSISTENT_TEXT_PLAIN, "Delayed Message".getBytes());
    
  • 缺点:

    • 依赖消息中间件,适用于 消息驱动的系统

(4) 分布式任务调度框架

  • 常见框架:
    • XXL-JOB(轻量级,适用于小规模定时任务)
    • Elastic-Job(基于 Zookeeper,适用于高并发调度)
    • Quartz + DB 持久化(适用于复杂定时任务)
  • 适用场景:
    • 定时任务执行
    • 任务分片调度
    • 可持久化任务队列

4. 结论

建议:如果是 单机应用,可以使用 DelayQueue;如果是 分布式微服务架构,建议使用 Redis ZSet / Kafka / RabbitMQ / 任务调度框架 实现延迟任务。

到此这篇关于Java实现DelayQueue延迟队列的文章就介绍到这了,更多相关Java DelayQueue延迟队列内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 全面解析JTA 深度历险

    全面解析JTA 深度历险

    本文介绍了利用 JTA 深度历险的相关内容,充分举例说明JTA的原理和事务处理等相关内容,十分详细,需要的朋友可以参考下。
    2017-09-09
  • Java实现Excel导入导出操作详解

    Java实现Excel导入导出操作详解

    在平常的办公工作中,导入导出excel数据是常见的需求,今天就来看一下通过Java如何来实现这个功能,感兴趣的朋友可以了解下
    2022-02-02
  • BigDecimal的toString()、toPlainString()和toEngineeringString()区别及用法详解

    BigDecimal的toString()、toPlainString()和toEngineeringString()区

    使用BigDecimal进行打印的时候,经常会对BigDecimal提供的三个toString方法感到好奇,以下整理3个toString方法的区别及用法,需要的朋友可以参考下
    2023-08-08
  • Java中关于Null的9个解释(Java Null详解)

    Java中关于Null的9个解释(Java Null详解)

    这篇文章主要介绍了Java中关于Null的9个解释(Java Null详解),本文详细讲解了Java中Null的9个相关知识,需要的朋友可以参考下
    2015-01-01
  • Springboot整合Shiro之加盐MD5加密的方法

    Springboot整合Shiro之加盐MD5加密的方法

    这篇文章主要介绍了Springboot整合Shiro之加盐MD5加密的方法,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-12-12
  • Java实现暴力匹配算法

    Java实现暴力匹配算法

    暴力匹配算法是一种简单的字符串匹配算法,本文主要介绍了Java实现暴力匹配算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • 利用Java中Calendar计算两个日期之间的天数和周数

    利用Java中Calendar计算两个日期之间的天数和周数

    Java 语言的Calendar(日历),Date(日期),和DateFormat(日期格式)组成了Java标准的一个基本但是非常重要的部分。日期是商业逻辑计算一个关键的部分。下面这篇文章就给大家介绍了如何利用Java中Calendar计算两个日期之间的天数和周数,下面来一起看看吧。
    2016-12-12
  • SpringBoot 如何从容器中获取对象

    SpringBoot 如何从容器中获取对象

    这篇文章主要介绍了SpringBoot 如何从容器中获取对象,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 在自定义线程池中捕获和处理异常实现过程

    在自定义线程池中捕获和处理异常实现过程

    这篇文章主要介绍了在自定义线程池中捕获和处理异常实现过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2026-05-05
  • SpringMVC自定义类型转换器实现解析

    SpringMVC自定义类型转换器实现解析

    这篇文章主要介绍了SpringMVC自定义类型转换器实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12

最新评论