调用 demo
java
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
class Task implements Delayed {
private long expire;
private String name;
public Task(String name, long delay, TimeUnit unit) {
this.name = name;
this.expire = System.currentTimeMillis() + unit.toMillis(delay);
}
@Override
public long getDelay(TimeUnit unit) {
long remain = expire - System.currentTimeMillis();
return unit.convert(remain, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() { return name; }
}
public class DelayQueueMain {
public static void main(String[] args) throws InterruptedException {
DelayQueue<Task> queue = new DelayQueue<>();
queue.put(new Task("1s任务", 1, TimeUnit.SECONDS));
queue.put(new Task("3s任务", 3, TimeUnit.SECONDS));
queue.put(new Task("2s任务", 2, TimeUnit.SECONDS));
for (int i = 0; i < 3; i++) {
Task t = queue.take();
// 取出到期:1s任务
// 取出到期:2s任务
// 取出到期:3s任务
System.out.println("取出到期:" + t);
}
}
}
成员变量
java
// 底层最小堆,存储延迟元素
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 全局独占锁,所有读写加锁
private final ReentrantLock lock = new ReentrantLock();
// 等待条件:无到期元素/队空时阻塞take线程
private final Condition available = lock.newCondition();
// Leader-Follower 优化标记
private Thread leader = null;
构造方法
java
// ========== 无参构造器 main new 时执行 ==========
public DelayQueue() {}
// 带集合构造器(main未使用,附原版)
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
入队方法
put
java
public void put(E e) {
offer(e);
}
add
java
public boolean add(E e) {
return offer(e);
}
offer(超时)
java
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
offer
java
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e); // 放入堆,自动上浮排序
// 如果新元素是堆顶,清空 leader 并唤醒 1 个线程
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
出队方法
take
java
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 可中断锁,响应线程interrupt
try {
for (;;) { // 死循环自旋校验队首
E first = q.peek();
// 分支1:队列为空,无限阻塞等待
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
// 分支2:队首已到期,直接弹出返回
if (delay <= 0)
return q.poll();
// 未到期,释放引用防止内存泄漏
first = null;
// 分支3:已有 leader 线程,当前线程永久阻塞
if (leader != null)
available.await();
else {
// 无leader,当前线程成为 leader,定时等待剩余延迟
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
// 等待结束,清空leader标记
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 释放锁前:如果无 leader 且队列有元素,唤醒一个等待线程
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
poll
java
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
// 空 或 未到期 返回null
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return q.poll();
} finally {
lock.unlock();
}
}
总结
- 无界阻塞队列:put/offer 永不阻塞,无容量限制
- 底层最小堆:队首一定是最快到期元素
- 线程安全:全局一把 ReentrantLock ,所有操作都会上锁
- leader 等待优化:减少多线程定时竞争,提升并发性能
- 仅到期可出队:take/poll 不会返回未到期元素,未到期线程阻塞
相关考点
first = null 为什么可以防止内存泄漏
不执行 first = null 时的引用关系如下:
- 阻塞线程的局部变量 first 引用堆顶延迟任务对象 Delayed Task
- DelayQueue 内部 PriorityQueue 也引用同一个 Task
当线程执行到 available.await() / awaitNanos() 阻塞时:
线程挂起、长时间休眠,线程栈帧不会销毁,局部变量 first 会一直存活
双重强引用导致 GC 无法回收
队列里本来就有一份引用,再加线程局部变量的强引用
当这个任务到期被别的线程 poll 取出、从堆里删掉,当前阻塞线程的变量 first 依旧死死持有该对象
只要线程没被唤醒、栈帧没退出,Task 及其关联大内存就无法被 GC,长时间大量阻塞线程会造成内存泄漏