DelayQueue
是 JUC 包(java.util.concurrent)
为我们提供的延迟队列,用于实现延时任务比如订单下单 15 分钟未支付直接取消。它是 BlockingQueue
的一种,底层是一个基于 PriorityQueue
实现的一个无界队列,是线程安全的。
DelayQueue
中存放的元素必须实现 Delayed
接口,并且需要重写 getDelay()
方法(计算是否到期)。
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
默认情况下, DelayQueue
会按照到期时间升序编排任务。只有当元素过期时(getDelay()
方法返回值小于等于 0),才能从队列中取出。
DelayQueue
最早是在 Java 5 中引入的,作为java.util.concurrent
包中的一部分,用于支持基于时间的任务调度和缓存过期删除等场景,该版本仅仅支持延迟功能的实现,还未解决线程安全问题。- 在 Java 6 中,
DelayQueue
的实现进行了优化,通过使用ReentrantLock
和Condition
解决线程安全及线程间交互的效率,提高了其性能和可靠性。 - 在 Java 7 中,
DelayQueue
的实现进行了进一步的优化,通过使用 CAS 操作实现元素的添加和移除操作,提高了其并发操作性能。 - 在 Java 8 中,
DelayQueue
的实现没有进行重大变化,但是在java.time
包中引入了新的时间类,如Duration
和Instant
,使得使用DelayQueue
进行基于时间的调度更加方便和灵活。 - 在 Java 9 中,
DelayQueue
的实现进行了一些微小的改进,主要是对代码进行了一些优化和精简。
总的来说,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();
}
}
从输出结果可以看出,即使笔者先提到 2s 到期的任务,1s 到期的任务 Task1 还是优先执行的。
Task 1
Task 2
Task 3
DelayQueue
底层是使用优先队列 PriorityQueue
来存储元素,而 PriorityQueue
采用二叉小顶堆的思想确保值小的元素排在最前面,这就使得 DelayQueue
对于延迟任务优先级的管理就变得十分方便了。同时 DelayQueue
为了保证线程安全还用到了可重入锁 ReentrantLock
,确保单位时间内只有一个线程可以操作延迟队列。最后,为了实现多线程之间等待和唤醒的交互效率,DelayQueue
还用到了 Condition
,通过 Condition
的 await
和 signal
方法完成多线程之间的等待唤醒。
DelayQueue
的实现是线程安全的,它通过 ReentrantLock
实现了互斥访问和 Condition
实现了线程间的等待和唤醒操作,可以保证多线程环境下的安全性和可靠性。
DelayQueue
通常用于实现定时任务调度和缓存过期删除等场景。在定时任务调度中,需要将需要执行的任务封装成延迟任务对象,并将其添加到 DelayQueue
中,DelayQueue
会自动按照剩余延迟时间进行升序排序(默认情况),以保证任务能够按照时间先后顺序执行。对于缓存过期这个场景而言,在数据被缓存到内存之后,我们可以将缓存的 key 封装成一个延迟的删除任务,并将其添加到 DelayQueue
中,当数据过期时,拿到这个任务的 key,将这个 key 从内存中移除。
Delayed
接口定义了元素的剩余延迟时间(getDelay
)和元素之间的比较规则(该接口继承了 Comparable
接口)。若希望元素能够存放到 DelayQueue
中,就必须实现 Delayed
接口的 getDelay()
方法和 compareTo()
方法,否则 DelayQueue
无法得知当前任务剩余时长和任务优先级的比较。
DelayQueue
和 Timer/TimerTask
都可以用于实现定时任务调度,但是它们的实现方式不同。DelayQueue
是基于优先级队列和堆排序算法实现的,可以实现多个任务按照时间先后顺序执行;而 Timer/TimerTask
是基于单线程实现的,只能按照任务的执行顺序依次执行,如果某个任务执行时间过长,会影响其他任务的执行。另外,DelayQueue
还支持动态添加和移除任务,而 Timer/TimerTask
只能在创建时指定任务。
没有回复内容