1. 程式人生 > >Java 集合框架分析:DelayQueue java8

Java 集合框架分析:DelayQueue java8

DelayQueue
目錄
DelayQueue簡單說明
主要方法分析
比較
總結

DelayQueue簡單說明

無界的阻塞佇列,和普通的佇列不同的是:裡面的元素只有時間過期了之後才能取出來,頭元素是已經過期的元素中最早過期的。如果沒有過期的元素,則poll方法返回null.但是size方法返回的所有的元素,包括過期的和沒有過期的。
注意:該集合的iterator並不保證訪問順序,你不能迭代是按時間順序的。
元素過期的定義:元素的getDelay方法返回一個小於或等於0的值。
注意:該容器中的元素必須實現Delayed介面

主要方法分析

1.主要的成員變數

    private
final transient ReentrantLock lock = new ReentrantLock(); private final PriorityQueue<E> q = new PriorityQueue<E>(); private Thread leader = null; private final Condition available = lock.newCondition();

從上面的成員可以看出:應該是用PriorityQueue q來進行元素的排序,用ReentrantLock 作為主鎖,用Condition 來形成佇列。
2 新增元素方法 offer,add、put直接呼叫的offer

public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            if (q.peek() == e) 
            {//如果當前堆上的頭元素變了,則leader無效,所有的元素強leader
                leader = null;
                available.signal();
            }
            return
true; } finally { lock.unlock(); } }

3 刪除元素take方法,阻塞版本

 /*過程
     a:獲取主鎖
     b:如果佇列為空,沒有元素,則等待在Condition上,醒來重試(執行a)
     c:如果當前佇列頭元素到期了,就直接可以取走了,跳到g.
         (這裡不是先判斷是不是有leader?說明leader的優先順序並不是絕的的)

     d:如果沒有到期,且有leader,就不關我什麼事了,因為當前元素到期了,也是leader取走了
     f:如果還沒有leader,那我就是leader吧,就等定長時間(當前的第一個元素到期時間)
     e:如果醒過來時,我還是leader(說明中途沒有插入更容易過期的元素),那我就將leader置空,然後跳到b
                 在下一輪中取走

     g:如果當前leader為null,且佇列不為空,則喚醒一個等待執行緒
     h:釋放鎖
     */
    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; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

結合offer和take看leader:offer時,如果堆上的元素不變,則是不會呼叫condition.signal()的。
從一個例子來看:

  1. queue為空。A1執行緒首先進來take,leader=null。A1發現佇列為空,則在conditon上等待。
  2. A2進來,同樣,等待。
  3. B進來offer一個元素,過期時間為3個時間單位後,此時新元素變成首位置,呼叫condition.signal,啟用A1或是A2。
  4. 假如A1獲取鎖,走一遍流程,此時首元素沒有過期,leader=null,則設定自己為leader,然後睡眠3個時間單位,醒來後重新獲取鎖後發現自己還是leader,那我就獲取元素了(這個時候不可能別的執行緒進入搶了,因為我把主鎖拿到了,且佇列上有元素到期了),在下次迴圈時,直接就取元素了。(如果沒有新的元素插入,則該執行緒會一直保持leader位置)
  5. 情況二:如果執行緒A1在等待1時間單位就被中斷了(有新的在一個時間單位內過期的元素插入了,重置了leader=null),但是這次A1並沒有搶到鎖,而是A2搶到鎖了,則A2搶到後,A2發現當前元素還沒有過期,且leade為null,則將自己設為leader,然後等待一個時間單位。如果等待一個時間單位後,同時如果有新的執行緒A3進來了,A3搶到了鎖,發現隊頂元素到期了,則直接取走了,然後直接釋放了鎖(因為leader!=null,所以A3不會喚醒佇列中的其他執行緒)。在A3釋放鎖後,A2獲取鎖成功,發現leader還是自己,然後重新探測佇列,發現佇列頭第一個元素還剩一個時間單位才過期,則又陷入等待。。。(隊頂元素已經變了,變成了之間的第一個插入的元素,隊頂元素不穩定,也就是為什麼執行緒執行緒在等待時,要釋放隊頂元素引用的原因,不然導致原先的那個元素一直得不到清理)。在等待1個時間單位後,如果沒有其他新的執行緒進入或是新的執行緒沒有獲取到鎖,A2又重新獲取了鎖,發現leader是自己(則下一次就能取走元素了,自己的leader位置就可以空出來了),則在下一次中取走元素,然後啟用等待中的一個執行緒,釋放鎖。

從上面分析可以看出:
1.leader位置在沒有新的更容易過期的元素插入的時候,是穩定的。此時leader執行緒比之前的執行緒優先順序更高。
2.leader執行緒如果處於睡眠狀態,在醒來後,不一定比新執行緒的優先順序高,這時,他們會搶奪主鎖,獲得主鎖的執行緒取走佇列頂層元素。
3.如果leader搶奪失敗,則新執行緒不會喚醒condition中的執行緒,而是leader又重新搶奪鎖。
4.condition中的執行緒會發生飢餓現象。如佇列中沒有新的更容易的過期的元素插入,且不斷的有新執行緒獲取的鎖,則condition中都不會有喚醒的機會。
5.leader在等待佇列頭元素過期時,釋放了當前的頭元素的引用,因為頭元素是不穩定的,且下一次leader並不能保證能獲取鎖,從該方法中返回,這樣就會導致該元素存活時間太長。

總結

1.不允許null元素
2.容器中的裡面的元素必須實現了 Delayed介面
3.取元素時,元素的要求是已經過期了的,如果沒有到期的,則阻塞版本的方法會阻塞。
4.也提供了阻塞版本的方法:如果當前佇列為空,或是沒有元素到期,則返回null.
5.目前還沒有想到這個容器的用途,但是之前看Redis時,有看到類似的東西。用於看過期鍵,如果鍵過期了則刪除該鍵,減少內容的損耗。可能這這種集合也是用來維護快取的吧!