本文目录导读:

Java标准库本身不包含名为“DelayQueue”的类作为API,但它通过 java.util.concurrent.DelayQueue 提供了对延迟队列的原生支持。
这是一个基于优先级队列(PriorityQueue)实现的无界阻塞队列,专门用于处理需要在未来某个时间点被消费的元素。
核心API及使用方法
关键类
java.util.concurrent.Delayed:元素必须实现的接口。java.util.concurrent.DelayQueue<E extends Delayed>:延迟队列本体。java.util.concurrent.BlockingQueue:DelayQueue实现了该接口,因此具备阻塞特性。
使用步骤
第一步:实现 Delayed 接口
你的任务对象需要实现 java.util.concurrent.Delayed 接口,并定义两个方法:
getDelay(TimeUnit unit):返回当前元素剩余的有效期。compareTo(Delayed o):定义队列中元素的排序规则(最早到期的元素在队首)。
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.Date;
public class Task implements Delayed {
private final String name;
private final long startTime; // 毫秒为单位
// delayTime: 从当前时刻算起,需要延迟的秒数
public Task(String name, long delayTime) {
this.name = name;
this.startTime = System.currentTimeMillis() + delayTime;
}
// 返回剩余的延迟时间
@Override
public long getDelay(TimeUnit unit) {
long diff = startTime - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
// 排序规则:剩余时间最少的排在最前面
@Override
public int compareTo(Delayed o) {
return Long.compare(this.startTime, ((Task) o).startTime);
}
@Override
public String toString() {
return "Task{" +
"name='" + name + '\'' +
", expireTime=" + new Date(startTime) +
'}';
}
}
第二步:使用 DelayQueue 进行生产和消费
import java.util.concurrent.DelayQueue;
public class DelayQueueExample {
public static void main(String[] args) throws InterruptedException {
DelayQueue<Task> delayQueue = new DelayQueue<>();
// 生产者:插入不同延迟时间的任务
System.out.println("插入任务,当前时间: " + new java.util.Date());
delayQueue.put(new Task("任务1", 2000)); // 2秒后到期
delayQueue.put(new Task("任务2", 5000)); // 5秒后到期
delayQueue.put(new Task("任务3", 1000)); // 1秒后到期
// 消费者:循环取出到期任务
while (true) {
// take() 会阻塞,直到有元素到期
Task task = delayQueue.take();
System.out.println("取出任务: " + task + " 当前时间: " + new java.util.Date());
// 3个任务都取出后退出循环(这里简单演示,取完手动退出)
if (delayQueue.isEmpty()) break;
}
}
}
输出示例:
插入任务,当前时间: Mon Jan 20 10:00:00 CST 2025
取出任务: Task{name='任务3', expireTime=Mon Jan 20 10:00:01 CST 2025} 当前时间: Mon Jan 20 10:00:01 CST 2025
取出任务: Task{name='任务1', expireTime=Mon Jan 20 10:00:02 CST 2025} 当前时间: Mon Jan 20 10:00:02 CST 2025
取出任务: Task{name='任务2', expireTime=Mon Jan 20 10:00:05 CST 2025} 当前时间: Mon Jan 20 10:00:05 CST 2025
重要方法与特点
offer(E e, long timeout, TimeUnit unit):与put类似,用于插入。take():阻塞式获取,等到第一个元素到期才返回,这是最常用的方法。poll():非阻塞式获取,如果没有到期元素,立即返回null。poll(long timeout, TimeUnit unit):带超时的阻塞式获取。peek():获取队首元素(但不移除),注意:它不会检查元素是否到期。- 无界队列:理论上可以无限添加元素,但过多元素会消耗大量内存。
实际应用场景
- 定时任务调度:按计划执行某些后台任务,如10分钟后发送通知。
- 会话超时管理:缓存用户会话,当会话过期时自动清除。
- 订单超时取消:电商中订单超过30分钟未支付,自动取消。
- 重试队列:将失败的任务放入延迟队列,5秒或10秒后重试。
注意
- 线程安全:
DelayQueue是线程安全的,可以在多线程环境中安全使用。 - 元素不可为null。
- 过期检查:
getDelay()方法返回的值必须是动态计算的(依据当前时间),因为队列内部会不断调用它来判断是否到期。 - 与
Timer/ScheduledThreadPoolExecutor的区别:DelayQueue是一个通用的数据结构,而Timer和ScheduledThreadPoolExecutor是完整的任务调度框架,前者更灵活,你可以手动控制何时取出任务;后者更常用于定时、周期性执行任务。
Java通过 java.util.concurrent.DelayQueue 提供了对延迟队列的直接、原生支持,你只需要让任务类实现 Delayed 接口并按规则编写 getDelay() 和 compareTo() 方法即可使用。