🔥 本文是DelayQueue系列的上篇,主要聚焦延迟队列的基础概念和实现原理。通过循序渐进的讲解,带你深入理解DelayQueue的核心机制和内部实现。
📚博主匠心之作,强推专栏:
一、认识DelayQueue
1.1 DelayQueue简介
- DelayQueue是Java并发包下的延时阻塞队列
- 继承自BlockingQueue接口,提供了阻塞式访问
- 只有延迟期满的元素才能被取出
- 队列头部是延迟最小的元素
DelayQueue本质上是一个支持延时获取元素的无界阻塞队列,队列中的元素必须实现Delayed接口。在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。
DelayQueue的基本定义:
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
// ...实现细节
}
1.2 核心特性
- 线程安全:内部使用ReentrantLock保证并发安全
- 延时功能:元素必须实现Delayed接口
- 优先级排序:底层使用PriorityQueue实现
- 阻塞特性:支持阻塞式获取和添加操作
DelayQueue的核心在于Delayed接口,所有放入队列的元素都必须实现这个接口。Delayed接口继承了Comparable接口,这意味着元素不仅要能计算剩余延迟时间,还需要支持排序功能,以便队列能够按照延迟时间的先后顺序进行排列。
Delayed接口定义:
public interface Delayed extends Comparable<Delayed> {
// 获取剩余延迟时间
long getDelay(TimeUnit unit);
}
getDelay方法返回的是剩余延迟时间,如果返回0或负数,则表示延迟已经到期,元素可以被取出。
1.3 快速入门示例
下面通过一个简单的示例来展示DelayQueue的基本用法。首先,我们需要创建一个实现了Delayed接口的任务类:
- 创建延时任务:
public class DelayedTask implements Delayed {
private String taskName;
private long executeTime; // 任务执行时间
public DelayedTask(String taskName, long delayTime) {
this.taskName = taskName;
this.executeTime = System.currentTimeMillis() + delayTime;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.executeTime, ((DelayedTask) o).executeTime);
}
@Override
public String toString() {
return "DelayedTask{taskName='" + taskName + "', executeTime=" + executeTime + "}";
}
}
在这个DelayedTask类中:
- taskName:任务名称,用于标识任务
- executeTime:任务的执行时间,等于当前时间加上延迟时间
- getDelay():计算剩余延迟时间,当返回值小于等于0时,表示任务可以执行了
- compareTo():用于在队列中按照执行时间排序,确保最先到期的任务在队列头部
接下来,我们使用DelayQueue来管理这些延时任务:
- 使用DelayQueue:
public class DelayQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 创建延时队列
DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
// 添加延时任务
delayQueue.offer(new DelayedTask("任务1", 2000)); // 2秒后执行
delayQueue.offer(new DelayedTask("任务2", 1000)); // 1秒后执行
delayQueue.offer(new DelayedTask("任务3", 3000)); // 3秒后执行
// 获取任务执行
while (!delayQueue.isEmpty()) {
DelayedTask task = delayQueue.take(); // 按延时时间顺序获取任务
System.out.println("执行任务:" + task);
}
}
}
/* 运行结果:
执行任务:DelayedTask{taskName='任务2', executeTime=1703123456789}
执行任务:DelayedTask{taskName='任务1', executeTime=1703123457789}
执行任务:DelayedTask{taskName='任务3', executeTime=1703123458789}
*/
在这个示例中:
- 我们创建了一个DelayQueue实例,用于存放DelayedTask对象
- 向队列中添加了三个不同延迟时间的任务
- 使用take()方法获取任务,这个方法会阻塞直到有任务到期
- 任务按照延迟时间的先后顺序被取出执行,而不是按照添加顺序
从运行结果可以看出,虽然任务1先添加,但任务2的延迟时间更短,所以任务2先被执行。这正是DelayQueue的核心特性:按照延迟到期时间排序,而不是按照添加顺序。
1.4 典型应用场景
- 订单超时自动取消
- 限时优惠券管理
- 缓存数据过期清理
- 定时任务调度
- 消息延时投递
DelayQueue在实际项目中有很多应用场景,最典型的就是需要在一定时间后执行的任务。下面以订单超时自动取消为例,展示DelayQueue的实际应用:
订单超时自动取消示例:
public class OrderTimeoutExample {
private static final DelayQueue<DelayedOrder> ORDER_QUEUE = new DelayQueue<>();
public static void main(String[]