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();
}
}