DelayQueue 源码解析
一、概述
DelayQueue
是一个无界阻塞队列,它提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素 (通过最小堆实现)。
DelayQueue
队列本身不具备存储能力,而是在内部通过 PriorityQueue
队列来实现存储功能和获取最近过期元素功能。
PriorityQueue
队列实现方案跟 PriorityBlockingQueue
相同,只是没有提供锁来保证并发安全。
本文涉及的知识点:
- 阻塞队列的概念:并发容器(一) — 综述
- 阻塞功能的实现涉及Condition接口:Condition接口
- 涉及的数据结构:二叉堆 - 最小堆
- 优先级队列:PriorityBlockingQueue(优先队列)
二、源码解析
关于队列的其他操作,参考:PriorityBlockingQueue(优先队列)
下面只分析如下几个过程:
- 入队阻塞
- 出队阻塞
- 延迟队列的功能如何实现
- 示例
1. 入队
/*
* 当前获取锁的线程
* Leader-Follower pattern
*/
private Thread leader = null;
/**
* Condition signalled when a newer element becomes available
* at the head of the queue or a new thread may need to
* become leader.
*/
private final Condition available = lock.newCondition();
public void put(E e) {
offer(e);
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e); //这里q(PriorityQueue)是线程不安全的优先队列
// 队列头的元素就是刚插入的元素
if (q.peek() == e) { //peek()获取队列头的元素(不移除队列头)
//老的leader置为null (因为老的leader是目标取走前一个队头的线程),再让接下来最先来取走队头的线程成为leader
leader = null;
available.signal(); //通知获取数据时等待的线程
}
return true;
} finally {
lock.unlock();
}
}
2. 出队
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {//自旋:循环获取数据,直到有数据到达延时时间,可以出队列为止。
E first = q.peek(); //获取优先队列的头元素
if (first == null) //如果成立,说明队列为空
available.await(); //让获取数据的线程进入等待队列
else { //执行到这里,说明队列不为空
//这里获取的时间表示延迟的截止时间跟当前时间差。小于等于0表示延迟时间到了
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll(); //poll()会将队列头元素移除队列
available.awaitNanos(delay);
// 执行到这里,说明还没有元素到达延迟时间,将first引用置空。
first = null; // don't retain ref while waiting
if (leader != null) //如果leader不为空,说明之前有线程在等待获取数据,所以当前线程是follower
available.await();
else {
// 当前没有线程要获取数据,所以将当前线程选举为leader
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//让当前线程进入等待队列等待剩余时间(避免频繁循环,消耗CPU),并释放锁。当这个等待时间抵达时,当前线程会重新获取到锁。
available.awaitNanos(delay);
} finally {
// 由于available.awaitNanos(delay)返回时获取了锁,且进入下一个循环肯定能拿到队列头元素,所以当前线程不再是leader。
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// leader == null表示当前没有获取数据的线程。
// q.peek() != null 表示队列还有数据,可以通知其他等待线程来获取。
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
3. 延迟功能的实现
/**
* 1.利用 PriorityQueue 队列堆顶最先达到延迟时间的特点。
* 2.获取堆顶元素,判断延时时间是否抵达,抵达则出队,否则进入下个循环。
*/
public E take() {
for (;;) {
E first = queue.peek(); //queue是PriorityQueue对象
if (first == null)
continue; //队列为空,进入下个循环
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0) {
return queue.poll();
}
}