併發容器(四) — DelayQueue 源碼解析

一、概述

DelayQueue 是一個無界阻塞隊列,它提供了在指定時間才能獲取隊列元素的功能,隊列頭元素是最接近過期的元素 (通過最小堆實現)。

DelayQueue 隊列本身不具備存儲能力,而是在內部通過 PriorityQueue 隊列來實現存儲功能和獲取最近過期元素功能。

PriorityQueue 隊列實現方案跟 PriorityBlockingQueue 相同,只是沒有提供鎖來保證併發安全。

本文涉及的知識點:

  1. 阻塞隊列的概念:併發容器(一) — 綜述
  2. 阻塞功能的實現涉及Condition接口:Condition接口
  3. 涉及的數據結構:二叉堆 - 最小堆
  4. 優先級隊列:PriorityBlockingQueue(優先隊列)

二、源碼解析

關於隊列的其他操作,參考:PriorityBlockingQueue(優先隊列)

下面只分析如下幾個過程:

  1. 入隊阻塞
  2. 出隊阻塞
  3. 延遲隊列的功能如何實現
  4. 示例

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章