阻塞佇列 DelayQueue 原始碼解析

槑!發表於2020-10-16

DelayQueue

  • 一個支援延時獲取元素的無界阻塞佇列,裡面的元素全部都是“可延期”的元素,**列頭的元素必須是最先“到期”**的元素,如果列頭不出隊,其他元素是無法出隊的。
  • 主要用於兩個方面:快取(清掉快取中超時的快取資料)、任務超時處理
  • 底層實現是優先佇列,在出隊時呼叫了內部優先佇列 peek 方法先查詢其隊首元素,如果到期了才呼叫優先佇列的 poll 讓隊首出隊,否則就阻塞等隊首到期,也就是說優先佇列中只有前一個元素到期並出隊後,後面的元素才能出隊。併發安全也是通過 Lock。
元素
  • 元素必須實現 Delayed 介面
// 實現該介面的getDelay()方法,同時定義compareTo()方法即可。
// 注意:ompareTo 方法提供的排序,必須與過期時間一致,不然就會出現最小堆堆頂並不是最先過期的元素
public interface Delayed extends Comparable<Delayed> {
    // 返回與此物件相關的的剩餘時間
    long getDelay(TimeUnit unit);
}
屬性
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
        implements BlockingQueue<E> {
    /** 可重入鎖 */
    private final transient ReentrantLock lock = new ReentrantLock();
    /** 支援優先順序的佇列,用的也是最小堆,無界非阻塞 */
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    /** leader 會超時等待,通常為第一個查詢到未到期頭節點的執行緒 */
    private Thread leader = null;
    /** 等待有可出隊元素 */
    private final Condition available = lock.newCondition();

    /**
     * 省略很多程式碼
     */
}
入隊
public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    // 獲取鎖
    lock.lock();
    try {
        // 向 PriorityQueue中插入元素
        q.offer(e);
        // 如果當前元素的對首元素(優先順序最高),leader設定為空,喚醒所有等待執行緒
        // peek 是查詢而非出隊
        if (q.peek() == e) {
            // 設定為 null 防止記憶體洩漏
            leader = null;
            available.signal();
        }
        // 無界佇列,永遠返回true
        return true;
    } finally {
        lock.unlock();
    }
}
出隊
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 獲取鎖
    lock.lockInterruptibly();
    try {
        for (;;) {
            // 查詢隊首元素
            E first = q.peek();
            // 隊首為空,阻塞,等待off()操作喚醒
            if (first == null)
                available.await();
            else {
                // 獲取對首元素的超時時間
                long delay = first.getDelay(NANOSECONDS);
                // <=0 表示已過期,出對,return
                if (delay <= 0)
                    // 只有這一個出口,只有隊首元素到期隊才可以出隊
                    return q.poll();
                
                // 不設為 null 會引起記憶體洩漏
                // 因為可能有多個執行緒來獲取隊首,結果沒到期全都阻塞,那麼這麼多個執行緒都持有了隊首元素的引用
                // 但是隊首到期成功出隊後,如果其他那麼多個執行緒還在阻塞,那這個隊首使用完就無法被回收
                first = null; // don't retain ref while waiting
                
                // leader != null 證明有比它來的早的執行緒已經在超時等待隊首到期了
                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 {
        // 喚醒阻塞執行緒(一般喚醒的是隊首後面阻塞等待出隊的元素)
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

相關文章