Java中常用的七個阻塞佇列第二篇DelayQueue原始碼介紹

kaizi1992發表於2020-04-23

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併發系列教程》:

 

相關文章