原创干货,欢迎关注△星标↑
朋友们,好久不见!博主最近状态起伏,先是忙得脚不沾地,接着打球又光荣负伤......(说多了都是泪😭)。总之,拖延症晚期患者终于回来填坑了!这么久没更新,实在对不住各位关注的兄弟姐妹。今天直接上硬货,给大家带来一个Java中非常强大但又常被忽略的神器------DelayQueue
延时队列。
学会它,不仅能搞定各种延时时调度、缓存过期场景,还能让你在面试中惊艳面试官!话不多说,直接开整!
🔍 1.什么是DelayQueue?
简单来说,DelayQueue
是 Java 并发包(java.util.concurrent
)里一个"超级能装 又超级有耐心"的无界阻塞队列。
它只接收实现了 Delayed
接口的"会员"元素,并且恪守一个原则:不到时间,绝不放行! 只有等元素的延迟时间到了,才能被取出来消费。
它的核心特性,4句话讲清楚:
✅ 延迟获取:队列里的元素,时间不到?概不发放!
✅ 无界队列:理论上可以无限添加(但小心OOM!)
✅ 线程安全:多线程并发操作?稳得一匹!内部实现了线程安全的访问机制。
✅ 内部排序 :基于优先队列PriorityQueue
实现,确保最快到期的元素在队列头部!
🧩 2.核心概念速览
2.1 核心接口:Delayed
要使用DelayQueue
,元素必须实现 Delayed
接口,该接口继承自 Comparable
接口:
csharp
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
getDelay(TimeUnit unit)
:返回剩余的延迟时间compareTo(Delayed o)
:用于比较两个元素的顺序(DelayQueue 内部使用),谁先到期谁先走
2.2 核心操作
方法 | 描述 |
---|---|
boolean add(E e) |
添加元素,等价于 offer() |
boolean offer(E e) |
添加元素,总是返回 true(无界队列) |
void put(E e) |
添加元素(阻塞直到成功,实际上不会阻塞,因为队列无界) |
E take() |
获取并移除队列头部元素,必要时等待直到有元素延迟到期 |
E poll() |
获取并移除队列头部元素,如果没有到期元素则返回 null |
E poll(long timeout, TimeUnit unit) |
获取并移除队列头部元素,必要时等待指定的时间 |
E peek() |
获取但不移除队列头部元素 |
2.3 核心原理
DelayQueue
内部使用 PriorityQueue
来存储元素,并使用 ReentrantLock
保证线程安全:
java
private final transient ReentrantLock lock = new ReentrantLock();
private final PriorityQueue<E> q = new PriorityQueue<E>();
private Thread leader = null;
private final Condition available = lock.newCondition();
关于延时队列的实现原理和核心源码分析,我们会在后面单独总结,这里就不做展开。
🚀 3.最佳实践(含代码实战)
光说不练假把式,下面直接上代码!
3.1 基础用法:实现一个延时任务
arduino
@Data
public class DelayTask implements Delayed {
private final String taskName;
private final long expireTime; // 绝对时间戳(纳秒)
public DelayTask(String taskName, long delay, TimeUnit unit) {
this.taskName = taskName;
// 计算未来到期的时间点
this.expireTime = System.nanoTime() + unit.toNanos(delay);
}
@Override
public long getDelay(TimeUnit unit) {
// 计算还剩多少时间
return unit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
}
@Override
public int compareTo(Delayed o) {
// 比较谁先到期,用于内部排序
return Long.compare(this.expireTime, ((DelayTask) o).expireTime);
}
}
使用效果:
arduino
public static void main(String[] args) throws InterruptedException {
DelayQueue<DelayTask> queue = new DelayQueue<>();
queue.put(new DelayTask("重要邮件", 4, TimeUnit.SECONDS));
queue.put(new DelayTask("系统提醒", 2, TimeUnit.SECONDS));
queue.put(new DelayTask("缓存清理", 6, TimeUnit.SECONDS));
System.out.println("开始监控时间: " + LocalTime.now());
while (!queue.isEmpty()) {
DelayTask task = queue.take(); // 这里会阻塞等待!
System.out.println("执行: " + task.getTaskName() + ",时间: " + LocalTime.now());
}
}
控制台输出:
makefile
开始监控时间: 22:27:03.857
执行: 系统提醒,时间: 22:27:05.820
执行: 重要邮件,时间: 22:27:07.821
执行: 缓存清理,时间: 22:27:09.822
看!任务严格按照预定的延迟时间执行了,是不是超简单?
3.2 进阶玩法:异步任务调度器
实际项目中,我们肯定不想阻塞主线程。来个异步调度器!
arduino
// 任务调度器(线程池版)
public class AsyncTaskScheduler {
private final DelayQueue<DelayTask> queue = new DelayQueue<>();
private final ExecutorService executor = Executors.newFixedThreadPool(10); // 线程池
// 启动调度线程
public void start() {
Thread dispatcher = new Thread(() -> {
while (true) {
try {
DelayTask task = queue.take();
executor.submit(task::execute); // 异步执行!
} catch (InterruptedException e) {
break;
}
}
});
dispatcher.setDaemon(true); // 设为守护线程
dispatcher.start();
}
public void scheduleTask(String name, long delay, TimeUnit unit) {
queue.offer(new DelayTask(name, delay, unit));
}
}
这样用:
arduino
scheduler.scheduleTask("每日数据报表", 1, TimeUnit.HOURS);
scheduler.scheduleTask("用户session检查", 30, TimeUnit.MINUTES);
// 放心去吧,到点自动执行!
3.3 实战场景:自动缓存清理
用它来实现一个轻量级缓存过期自动清理功能,简直完美!
csharp
public class ExpiringCache<K, V> {
private final Map<K, V> cacheMap = new ConcurrentHashMap<>();
private final DelayQueue<CacheItem<K>> expireQueue = new DelayQueue<>();
public void put(K key, V value, long ttl, TimeUnit unit) {
cacheMap.put(key, value);
expireQueue.put(new CacheItem<>(key, ttl, unit));
}
// 启动清理线程
private void startCleaner() {
Thread cleaner = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
CacheItem<K> expiredItem = expireQueue.take();
cacheMap.remove(expiredItem.getKey());
} catch (InterruptedException e) {
return;
}
}
});
cleaner.setDaemon(true);
cleaner.start();
}
}
💡 四、总结与避坑指南
总结一下,DelayQueue
的核心优势:
- 精准延时:时间控制精度高,任务调度靠谱。
- 简单易用:API 简洁明了,几行代码就能搭建强大功能。
- 线程安全:并发场景下表现优异,无需额外同步。
⚠️ 使用注意(避坑指南):
- 内存限制:作为无界队列,狂塞任务可能导致 OOM!务必做好容量控制。
- 时间精度 :默认使用
System.nanoTime()
,精度高但别跨机器用,否则时间可能对不上。 - 任务持久化:队列任务存在内存里,重启就没了!重要任务记得持久化。
延迟队列用得好,开发效率低不了!缓存定时和调度,一个
DelayQueue
全撂倒!
希望这篇干货能帮你彻底拿下 DelayQueue
!如果觉得有用,欢迎点赞、在看、转发三连✨,也欢迎在评论区交流你的使用心得!