简介
DelayQueue是一个支持延迟获取元素的无界阻塞队列,队列内部组合PriorityQueue优先级队列来实现元素的超时获取,队列中的元素必须实现Delayed接口,在创建元素的时候,指定多久之后获取元素,只有在延时期满的时候才能够获取。
DelayQueue使用Leader/Follower多线程网络模型,每次只能有一个leader,多个follower
对PriorityQueue优先级队列不太明白的可以看PriorityQueue源码分析
源码分析
主要属性
private final transient ReentrantLock lock = new ReentrantLock(); // 排它锁
private final PriorityQueue<E> q = new PriorityQueue<E>(); // 线程非安全的优先级队列
private Thread leader = null; // leader 线程
private final Condition available = lock.newCondition(); // 等待队列
主要方法
入队
public boolean offer(E e) {
final ReentrantLock lock = this.lock; // 获取独占锁
lock.lock(); // 加锁
try {
q.offer(e); // 加入优先级队列
if (q.peek() == e) { // 如果当前加入的元素在堆顶
leader = null; // leader = null 这样可以重新选择新的leader
available.signal(); // 唤醒一个等待的线程
}
return true;
} finally {
lock.unlock();
}
}
当前添加的元素如果是堆顶元素,就唤醒一个等待的线程。
这里有两种情况:
- 堆中不存在元素,如果存在先get后put情况,这里就通知等待的线程,堆中存在元素了
- 堆中有其它元素,当前添加的元素是堆顶元素,也唤醒一个等待的线程,其实这里的唤醒一般就是重新选举leader
出队
public E poll() {
final ReentrantLock lock = this.lock; // 获取独占锁
lock.lock(); // 加锁
try {
E first = q.peek(); // 获取堆顶元素
if (first == null || first.getDelay(NANOSECONDS) > 0) // 如果没有元素或者堆顶元素还没有过有效期,直接返回null
return null;
else
return q.poll(); // 获取堆顶元素
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout); // 超时获取元素,时间转为纳秒
final ReentrantLock lock = this.lock; // 独占锁
lock.lockInterruptibly(); // 可中断获取锁
try {
for (;;) {
E first = q.peek(); // 堆顶元素
if (first == null) { // 如果堆中没有元素
if (nanos <= 0) // 超时获取元素,已经超时,直接返回null
return null;
else
nanos = available.awaitNanos(nanos); // 堆中存在元素且还没过有效期,挂起剩余的时间
} else { // 堆中存在元素
long delay = first.getDelay(NANOSECONDS); // 堆顶元素剩余的时间
if (delay <= 0) // 堆顶元素时间已到
return q.poll(); // 返回堆顶元素
if (nanos <= 0) // 堆顶元素还没过有效期,但当前线程已经超时,直接返回null
return null;
/**
* 说明下这里first=null的目的:
* a线程过来,检查到堆顶元素没有超时,在下面被挂起了
* b线程从await中苏醒,拿走了堆顶元素
* 接下来,如果发生gc,被拿走的元素是要被gc的,如果在这里first还指向那个元素,是gc不了的
*/
first = null; // don't retain ref while waiting
if (nanos < delay || leader != null) //超时等待时间小于 堆顶元素超时时间或者已经存在leader
nanos = available.awaitNanos(nanos); // 挂起超时等待的时间
else { // 超时等待时间 > 延迟时间 并且没有其它线程在等待,那么当前元素成为leader,表示leader 线程最早 正在等待获取元素
Thread thisThread = Thread.currentThread(); // 拿到当前线程
leader = thisThread; // leader 指向当前线程
try {
long timeLeft = available.awaitNanos(delay); // 挂起堆顶元素剩余(多久超时)的时间,并返回剩余等待的时间
/*
delay: 堆顶元素的过期时间
timeLeft: awaitNanos 剩余的时间
nanos -= delay - timeLeft; 剩余的超时时间
*/
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread) // 如果leader还指向当前线程,说明堆顶元素没有变化
leader = null; // 清空leader,让其它线程有机会成为leader
}
}
}
}
} finally {
if (leader == null && q.peek() != null) // 如果leader是null且队列中有元素
/*
唤醒一个等待的线程,让它有机会成为leader,但唤醒的这个线程不一定能够成为leader,
如果在当前线程释放锁之后,此时有新的线程过来,新的线程且拿到了锁,新的线程可能会成为leader
leader 只能有一个
*/
available.signal();
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); // 堆顶元素还有多久超时
if (delay <= 0) // 如果已经超时
return q.poll(); // 返回堆顶元素
/**
* 说明下这里first=null的目的:
* a线程过来,检查到堆顶元素没有超时,在下面被挂起了
* b线程从await中苏醒,拿走了堆顶元素
* 接下来,如果发生gc,被拿走的元素是要被gc的,如果在这里first还指向那个元素,是gc不了的
*/
first = null; // don't retain ref while waiting
if (leader != null) // 已经有leader了
available.await(); // 无期限挂起
else { // 还没有leader
Thread thisThread = Thread.currentThread(); // 拿到当前线程
leader = thisThread; // leader 指向当前线程
try {
available.awaitNanos(delay); // 挂起堆顶元素剩余(多久超时)的时间
} finally {
if (leader == thisThread) // 如果leader还指向当前线程,说明堆顶元素没有变化
leader = null; // 清空leader,让其它线程有机会成为leader
}
}
}
}
} finally {
if (leader == null && q.peek() != null) // 如果leader是null且队列中有元素
/*
唤醒一个等待的线程,让它有机会成为leader,但唤醒的这个线程不一定能够成为leader,
如果在当前线程释放锁之后,此时有新的线程过来,新的线程且拿到了锁,新的线程可能会成为leader
leader 只能有一个
*/
available.signal();
lock.unlock(); // 释放锁
}
}
Condition 条件在阻塞时会释放锁,在被唤醒时会再次获取锁,获取成功才会返回。 当进行超时等待时,阻塞在Condition 上后会释放锁,一旦释放了锁,那么其它线程就有可能参与竞争,某一个线程就可能会成为leader,leader是用来减少不必要的竞争,如果leader不为空说明已经有线程在取了,设置当前线程等待即可。(leader 就是一个信号,告诉其它线程:你们不要再去获取元素了,它们延迟时间还没到期,我都还没有取到数据呢,你们要取数据,等我取了再说)