DelayQueue可以说是加了定时的PriorityBlockingQueue,它也是最小堆的结构,不过节点的取出必须要等到延迟的时间。
一、内部代码结构
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
private final transient ReentrantLock lock = new ReentrantLock();
// 延迟队列是基于PriorityQueue保存数据的,PriorityQueue内部结构跟PriorityBlockingQueue基本一致,只不过它是是非线程安全,非阻塞的
private final PriorityQueue<E> q = new PriorityQueue<E>();
/** 第一个等待取出节点的线程,如果leader不为空,说明队列不为空并且第一个节点被标记等待取出,后面出队列方法会讲到 */
private Thread leader = null;
/** 出队列的等待队列 */
private final Condition available = lock.newCondition();
}
二、入队列
public void put(E e) {
// 直接调用offer方法
offer(e);
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
// 首先加锁
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {
// 第一个节点等于刚刚入队列的节点,说明这个节点是最小的,
// 需要第一个出队列,所以唤醒等待的线程
leader = null;
// 唤醒等待出队列的线程
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
三、出队列
public E poll() {
final ReentrantLock lock = this.lock;
// 首先加锁
lock.lock();
try {
E first = q.peek();
// 第一个节点不为空,并且延迟时间小于等于0才会出队列
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return q.poll();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 加锁
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
// 如果第一个节点为空则无限等待,直到被唤醒
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
// 第一个节点不为空,并且延迟时间小于等于0,则出队列
if (delay <= 0)
return q.poll();
// 这里置为null是很有必要的,这样当first对应的节点被删除或者出队列后可以被回收掉
first = null; // don't retain ref while waiting
// leader不为空说明已经有线程在等待第一个节点了
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 阻塞等待剩余时间
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// leader为空并且第一个节点不为空,则唤醒其他等待出队列的线程
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
四、总结
- DelayQueue的数据结构是基于PriorityQueue实现的,也是最小堆的结构。它的延迟是通过每个节点都实现Delayed接口的getDelay(TimeUnit unit)方法实现的,即出队列的第一个节点要延迟规定的时间才会出队列成功。
- 内部出入队列,删除节点操作都是通过重入锁来做并发控制。
- 出队列用了leader线程去等待第一个节点延迟固定的时间。其他并发出队列的线程会做阻塞直到唤醒,而不是同样阻塞等待固定的时间,这样的设计可以减少线程调度,较少锁竞争的冲突。