Java中常用的七個阻塞佇列第二篇DelayQueue原始碼介紹
通過前面兩篇文章,我們對佇列有了瞭解及已經認識了常用阻塞佇列中的三個了。本篇我們繼續介紹剩下的幾個佇列。
本文主要內容:通過原始碼學習Delayqueue及理解Dqueue並用程式碼簡單演示使用場景。
本文出自凱哥Java(kaigejava)的《凱哥Java併發系列》之《Java併發程式設計之佇列》系列的第三篇:《Java中常用的七個阻塞佇列第二篇DelayQueue原始碼介紹》
Java中常用的幾個佇列中,阻塞佇列還有四個沒介紹。如下圖:
DelayQueue
先上總結腦圖:
編輯
來看看構造器:
支援無參和支援直接存放一個集合的。
再來看看為什麼說DQueue佇列使用的是PriorityQueue實現的呢?
來看看原始碼:
在新增元素的offer方法原始碼中,我們可以看到最終呼叫的是q.offer(e)這個方法的。那麼q又是什麼呢?我們接著跟下去:private final PriorityQueue<E> q = new PriorityQueue<E>();。發現q是PriorityQueue這個佇列。如下圖:
為什麼說可以延時呢?
我們來看看DQueyue類的定義:
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {}
從原始碼中,我們可以看到DQueue佇列中存放的元素必須要實現Delayed介面。
我們在來看看Delayed介面的方法:
只有,long getDelay(TimeUnit unit);方法。設定等待時間。
在從佇列中獲取資料的時候會對超時時間進行判斷的。當超時時間小於等於0的時候,才會呼叫priorityQueue佇列的poll()方法。具體原始碼如下:
從原始碼中,我們可以看到,DQueue延時處理的:
無界怎麼理解?
說DQueue無界,我們應該從原始碼中查詢。DQueue佇列是基於PriorityQueue佇列來實現的。那麼我們就來看看PriorityQueue佇列新增元素的原始碼。
從上圖中,我們可以看到,在新增元素的時候offer方法會進行判斷,當i的值大於等於佇列的長的時候,會呼叫grow()方法來進行擴容。在grow方法中,我們可以看到會使用Arrsys.copyof()方法複製一份給佇列。這樣佇列就完成了庫容。沒有大小的限制。所以說是無界的。
阻塞理解
當佇列為空的時候,“獲取/取”元素操作將會block,被阻塞著。我們來看看原始碼是怎麼實現的。
從原始碼中我們可以看到,當從佇列中獲取元素的時候,先判斷,如果第一個元素為空的時候,就等待。當等待的時間小於等於延時時間的話,就從佇列中poll了;如果leader不為空的話,說明當前佇列不是隊首元素,依然await。
支援優先順序
因為在PQueue佇列的新增方法中,使用了comparator.compare方法。原始碼如下圖:
所以通過原始碼分析我們可以到得到DelayQueue如下腦圖:
使用場景:
DQueue非常有用的。我們利用DQueue的延時特性,可以講DQueue應用於以下場景:
1:快取的設計。可以利用Dqueue儲存快取元素的有效期。使用一個執行緒迴圈的從佇列中獲取資料。一旦獲取到資料,就說明快取有效期到了。
2:定時任務排程。可以使用Dqueue儲存需要執行的任務和任務執行的時間,一旦從DQueue中獲取到了任務,就開始執行任務了。比如TimerQueue就是使用了DelayQueue來實現的。
下面凱哥(凱哥Java:kaigejava)通過程式碼簡單演示模擬快取過期時間的案例。
程式碼演示:
需求:模擬快取設定有效期。
說明:當從佇列中獲取到元素,說明元素的有效期到了。
模擬快取的物件:
構造器:
需要注意:time=傳遞的time+當前時間。
實現了Delayed介面,需要重寫getDelay和compartTo方法。
重寫方法如下:
返回的是time與當前時間之間的差值。
compareTo方法如下:
呼叫方法:
來看看執行結果:
從執行結果中,我們可以看到,從列印出開始獲取到k1的輸出之間相差1s;K1與k2之間相差2s;K3和K2之間也相差2s.符合我們上面預設的時間差。
《Java併發系列教程》: