一、簡介
上一篇咱們介紹了 LinkedBlockingDeque
的兄弟篇 LinkedBlockingQueue
。聽名字也知道一個實現了 Queue
接口,一個實現了 Deque
接口,因爲 Deque
接口又繼承於 Queue
,因此 LinkedBlockingDeque
天然就有 LinkedBlockingQueue
的全部方法,而且還提供了雙端隊列的一些其餘方法,不清除隊列相關類的繼承關係的童鞋,請移步看我以前的文章:說說隊列Queue,下面的這張圖就是該文章中的。java
二、源碼分析
2.一、屬性
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
/** * 節點類,維護了前一個元素和後一個元素,用來存儲數據 */ 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();
由這些屬性,咱們能夠和 LinkedBlockingQueue
進行對比。node
首先是Node節點類,不一樣於 LinkedBlockingQueue
的單向鏈表,LinkedBlockingDeque
維護的是一個雙向鏈表。併發
再來看count,這裏是用int來進行修飾,而 LinkedBlockingQueue
確實用的AtomicInteger來修飾,這裏這麼作是由於 LinkedBlockingDeque
內部的每個操做都共用一把鎖,故能保證可見性。而 LinkedBlockingQueue
中維護了兩把鎖,在添加和移除元素的時候並不能保證雙方可以看見count的修改,因此使用CAS來維護可見性。ide
2.二、構造函數
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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(); } }
構造函數幾乎和 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相關的方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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"); }
add調用的實際上是addLast方法,而addFirst和addLast都調用的offer的相關方法,這裏直接看offer的方法。spa
offer相關的方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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(); } }
很明顯,加鎖之後調用linkFirst和linkLast這兩個方法。code
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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; }
下面給出兩張圖,都是隊列爲空的狀況下,調用linkFirst和linkLast依次放入元素A和元素B的圖:對象
offer的超時方法這裏就不放出了,原理和 LinkedBlockingQueue
同樣,利用了Condition的awaitNanos進行超時等待,並在外面用while循環控制等待時的中斷問題。
put相關的方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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(); } }
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相關的方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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; }
remove方法調用了poll的相關方法。
poll相關的方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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(); } }
poll方法用lock加鎖後分別調用了unlinkFirst和unlinkLast方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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; }
poll的超時方法也是利用了Condition的awaitNanos來作超時等待。這裏就不作過多說明了。
take相關的方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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(); } }
仍是一個套路,lock加鎖,while循環重試移除,await阻塞等待。
2.3.三、獲取元素方法
獲取元素的方法有element和peek兩種方法。
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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(); } }
獲取元素前加鎖,防止併發問題致使數據不一致。利用first和last節點直接能夠得到元素。
2.3.四、刪除元素方法
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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(); } }
刪除元素是從頭/尾向兩邊進行遍歷比較,故時間複雜度爲O(n),最後調用unlink把要移除元素的prev和next進行關聯,把要移除的元素從鏈中脫離,等待下次GC回收。
三、總結
LinkedBlockingDeque和LinkedBlockingQueue的相同點在於:
- 基於鏈表
- 容量可選,不設置的話,就是Int的最大值
和LinkedBlockingQueue的不一樣點在於:
- 雙端鏈表和單鏈表
- 不存在頭節點
- 一把鎖+兩個條件