【java多線程】隊列系統之LinkedBlockingDeque源碼

一、簡介

上一篇咱們介紹了 LinkedBlockingDeque 的兄弟篇 LinkedBlockingQueue 。聽名字也知道一個實現了 Queue 接口,一個實現了 Deque 接口,因爲 Deque 接口又繼承於 Queue ,因此 LinkedBlockingDeque 天然就有 LinkedBlockingQueue 的全部方法,而且還提供了雙端隊列的一些其餘方法,不清除隊列相關類的繼承關係的童鞋,請移步看我以前的文章:說說隊列Queue,下面的這張圖就是該文章中的。java

二、源碼分析

2.一、屬性

/** * 節點類,維護了前一個元素和後一個元素,用來存儲數據 */
static final class Node<E> { E item; Node<E> prev; Node<E> next; Node(E x) { item = x; } } /** * 阻塞隊列的第一個元素的節點 */
transient Node<E> first; /** * 阻塞隊列的尾節點 */
transient Node<E> last; /** 當前阻塞隊列中的元素個數 */
private transient int count; /** 阻塞隊列的大小,默認爲Integer.MAX_VALUE */
private final int capacity; /** 全部訪問元素時使用的鎖 */
final ReentrantLock lock = new ReentrantLock(); /** 等待take的條件對象 */
private final Condition notEmpty = lock.newCondition(); /** 等待put的條件對象 */
private final Condition notFull = lock.newCondition();
View Code

由這些屬性,咱們能夠和 LinkedBlockingQueue 進行對比。node

首先是Node節點類,不一樣於 LinkedBlockingQueue 的單向鏈表,LinkedBlockingDeque 維護的是一個雙向鏈表。併發

再來看count,這裏是用int來進行修飾,而 LinkedBlockingQueue 確實用的AtomicInteger來修飾,這裏這麼作是由於 LinkedBlockingDeque 內部的每個操做都共用一把鎖,故能保證可見性。而 LinkedBlockingQueue 中維護了兩把鎖,在添加和移除元素的時候並不能保證雙方可以看見count的修改,因此使用CAS來維護可見性。ide

2.二、構造函數

public LinkedBlockingDeque() { this(Integer.MAX_VALUE); } public LinkedBlockingDeque(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; } public LinkedBlockingDeque(Collection<? extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock lock = this.lock; lock.lock(); try { for (E e : c) { if (e == null) throw new NullPointerException(); if (!linkLast(new Node<E>(e))) throw new IllegalStateException("Deque full"); } } finally { lock.unlock(); } }
View Code

構造函數幾乎和 LinkedBlockingQueue 同樣,不過少了一句 last = head = new Node<E>(null) 。由於這裏不存在head節點了,而用first來代替。而且添加元素的方法也進行了重寫來適應 Deque 的方法。函數

2.三、方法

LinkedBlockingQueue中有的方法該類中都會出現,無外乎多了隊列的兩端操做。這裏爲了方便,我會放在一塊兒來進行說明。源碼分析

2.3.一、入隊方法

LinkedBlockingDeque提供了多種入隊操做的實現來知足不一樣狀況下的需求,入隊操做有以下幾種:this

  • add(E e)、addFirst(E e)、addLast(E e)
  • offer(E e)、offerFirst(E e)、offerLast(E e)
  • offer(E e, long timeout, TimeUnit unit)、offerFirst(E e, long timeout, TimeUnit unit)、offerLast(E e, long timeout, TimeUnit unit)
  • put(E e)、putFirst(E e)、putLast(E e)

add相關的方法

public boolean add(E e) { addLast(e); return true; } public void addFirst(E e) { if (!offerFirst(e)) throw new IllegalStateException("Deque full"); } public void addLast(E e) { if (!offerLast(e)) throw new IllegalStateException("Deque full"); }
View Code

add調用的實際上是addLast方法,而addFirst和addLast都調用的offer的相關方法,這裏直接看offer的方法。spa

offer相關的方法

public boolean offer(E e) { return offerLast(e); } public boolean offerFirst(E e) { if (e == null) throw new NullPointerException(); Node<E> node = new Node<E>(e); final ReentrantLock lock = this.lock; lock.lock(); try { return linkFirst(node); } finally { lock.unlock(); } } public boolean offerLast(E e) { if (e == null) throw new NullPointerException(); Node<E> node = new Node<E>(e); final ReentrantLock lock = this.lock; lock.lock(); try { return linkLast(node); } finally { lock.unlock(); } }
View Code

很明顯,加鎖之後調用linkFirst和linkLast這兩個方法。code

private boolean linkFirst(Node<E> node) { if (count >= capacity) return false; Node<E> f = first; node.next = f; first = node; // 插入第一個元素的時候才須要把last指向該元素,後面全部的操做只須要把f.prev指向node
    if (last == null) last = node; else f.prev = node; ++count; notEmpty.signal(); return true; } private boolean linkLast(Node<E> node) { if (count >= capacity) return false; Node<E> l = last; node.prev = l; last = node; if (first == null) first = node; else l.next = node; ++count; notEmpty.signal(); return true; }
View Code

下面給出兩張圖,都是隊列爲空的狀況下,調用linkFirst和linkLast依次放入元素A和元素B的圖:對象

offer的超時方法這裏就不放出了,原理和 LinkedBlockingQueue 同樣,利用了Condition的awaitNanos進行超時等待,並在外面用while循環控制等待時的中斷問題。

put相關的方法

public void put(E e) throws InterruptedException { putLast(e); } public void putFirst(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); Node<E> node = new Node<E>(e); final ReentrantLock lock = this.lock; lock.lock(); try { // 阻塞等待linkFirst成功
        while (!linkFirst(node)) notFull.await(); } finally { lock.unlock(); } } public void putLast(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); Node<E> node = new Node<E>(e); final ReentrantLock lock = this.lock; lock.lock(); try { // 阻塞等待linkLast成功
        while (!linkLast(node)) notFull.await(); } finally { lock.unlock(); } }
View Code

lock加鎖後一直阻塞等待,直到元素插入到隊列中。

2.3.二、出隊方法

入隊列的方法說完後,咱們來講說出隊列的方法。LinkedBlockingDeque提供了多種出隊操做的實現來知足不一樣狀況下的需求,以下:

  • remove()、removeFirst()、removeLast()
  • poll()、pollFirst()、pollLast()
  • take()、takeFirst()、takeLast()
  • poll(long timeout, TimeUnit unit)、pollFirst(long timeout, TimeUnit unit)、pollLast(long timeout, TimeUnit unit)

remove相關的方法

public E remove() { return removeFirst(); } public E removeFirst() { E x = pollFirst(); if (x == null) throw new NoSuchElementException(); return x; } public E removeLast() { E x = pollLast(); if (x == null) throw new NoSuchElementException(); return x; }
View Code

remove方法調用了poll的相關方法。

poll相關的方法

public E poll() { return pollFirst(); } public E pollFirst() { final ReentrantLock lock = this.lock; lock.lock(); try { return unlinkFirst(); } finally { lock.unlock(); } } public E pollLast() { final ReentrantLock lock = this.lock; lock.lock(); try { return unlinkLast(); } finally { lock.unlock(); } }
View Code

poll方法用lock加鎖後分別調用了unlinkFirst和unlinkLast方法

private E unlinkFirst() { Node<E> f = first; if (f == null) return null; Node<E> n = f.next; E item = f.item; f.item = null; f.next = f; // help GC // first指向下一個節點
    first = n; if (n == null) last = null; else n.prev = null; --count; notFull.signal(); return item; } private E unlinkLast() { Node<E> l = last; if (l == null) return null; Node<E> p = l.prev; E item = l.item; l.item = null; l.prev = l; // help GC // last指向下一個節點
    last = p; if (p == null) first = null; else p.next = null; --count; notFull.signal(); return item; }
View Code

poll的超時方法也是利用了Condition的awaitNanos來作超時等待。這裏就不作過多說明了。

take相關的方法

public E take() throws InterruptedException { return takeFirst(); } public E takeFirst() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lock(); try { E x; while ( (x = unlinkFirst()) == null) notEmpty.await(); return x; } finally { lock.unlock(); } } public E takeLast() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lock(); try { E x; while ( (x = unlinkLast()) == null) notEmpty.await(); return x; } finally { lock.unlock(); } }
View Code

仍是一個套路,lock加鎖,while循環重試移除,await阻塞等待。

2.3.三、獲取元素方法

獲取元素的方法有element和peek兩種方法。

public E element() { return getFirst(); } public E peek() { return peekFirst(); } public E getFirst() { E x = peekFirst(); if (x == null) throw new NoSuchElementException(); return x; } public E getLast() { E x = peekLast(); if (x == null) throw new NoSuchElementException(); return x; } public E peekFirst() { final ReentrantLock lock = this.lock; lock.lock(); try { return (first == null) ? null : first.item; } finally { lock.unlock(); } } public E peekLast() { final ReentrantLock lock = this.lock; lock.lock(); try { return (last == null) ? null : last.item; } finally { lock.unlock(); } }
View Code

獲取元素前加鎖,防止併發問題致使數據不一致。利用first和last節點直接能夠得到元素。

2.3.四、刪除元素方法

public boolean remove(Object o) { return removeFirstOccurrence(o); } public boolean removeFirstOccurrence(Object o) { if (o == null) return false; final ReentrantLock lock = this.lock; lock.lock(); try { // 從first向後開始遍歷比較,找到元素後調用unlink移除
        for (Node<E> p = first; p != null; p = p.next) { if (o.equals(p.item)) { unlink(p); return true; } } return false; } finally { lock.unlock(); } } public boolean removeLastOccurrence(Object o) { if (o == null) return false; final ReentrantLock lock = this.lock; lock.lock(); try { // 從last向前開始遍歷比較,找到元素後調用unlink移除
        for (Node<E> p = last; p != null; p = p.prev) { if (o.equals(p.item)) { unlink(p); return true; } } return false; } finally { lock.unlock(); } } void unlink(Node<E> x) { Node<E> p = x.prev; Node<E> n = x.next; if (p == null) { unlinkFirst(); } else if (n == null) { unlinkLast(); } else { p.next = n; n.prev = p; x.item = null; // Don't mess with x's links. They may still be in use by // an iterator.
        --count; notFull.signal(); } }
View Code

刪除元素是從頭/尾向兩邊進行遍歷比較,故時間複雜度爲O(n),最後調用unlink把要移除元素的prev和next進行關聯,把要移除的元素從鏈中脫離,等待下次GC回收。

三、總結

LinkedBlockingDeque和LinkedBlockingQueue的相同點在於:

  1. 基於鏈表
  2. 容量可選,不設置的話,就是Int的最大值

和LinkedBlockingQueue的不一樣點在於:

  1. 雙端鏈表和單鏈表
  2. 不存在頭節點
  3. 一把鎖+兩個條件
相關文章
相關標籤/搜索