為什么先介紹LinkedBlockingDeque雙向同步隊列呢,主要是它比較簡單卑惜,對于后面的隊列的理解是一個好的鋪墊
結構
- Node<E> first 頭節(jié)點
- Node<E> last 尾節(jié)點
- int count 節(jié)點數量
- int capacity 容量
- ReentrantLock lock = new ReentrantLock(); 鎖
- Condition notEmpty = lock.newCondition(); 非空條件變量
- Condition notFull = lock.newCondition(); 未滿條件變量
由上可以看出瘾带,LinkedBlockingDeque有一個獨占鎖鼠哥,且包括它的兩個條件變量,其實這就是一個生產者-消費者模型看政。
再來看其內部Node類
static final class Node<E> {
E item;
Node<E> prev;
Node<E> next;
Node(E x) {
item = x;
}
}
由上可以看出與ConcurrentLinkedQueue不同朴恳,其內部是一個雙端隊列。
到此我們值了解這么多允蚣,重要的操作原理我們往下看源碼
重要方法
因為是隊列于颖,離不開offer、poll嚷兔、peek這幾個重要方法森渐,不過由于LinkedBlockingDeque是一個雙向隊列所以還有對應相反的方法。
入隊操作
入隊方法有如下:
```
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");
}
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); // 直接返回linkFirst
} 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();
}
}
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 {
while (!linkFirst(node)) // 如果失敗冒晰,則調用notFull.await,也就是同衣,等待未滿條件
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 {
while (!linkLast(node))
notFull.await();
} finally {
lock.unlock();
}
}
```
從代碼中我們可以看到,一共有三類:add壶运、offer乳怎、put,然后分別有對應last和first的方法。只有三點不同:
- put中加個while循環(huán)蚪缀,如果失敗,則進行等待恕出,只有l(wèi)ink成功的條件才往下執(zhí)行询枚,而offer直接返回link的結果
- 它們的返回參數不一樣,offer的是boolean浙巫,而put則是void
- add函數在失敗后會拋"Deque full"的異常
于是我們知道金蜀,offer不保證成功,put保證一定能插入元素且是阻塞的方法的畴。
接著我們繼續(xù)看他們的核心函數link:
private boolean linkFirst(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false; // 數量達到限制渊抄,則返回false
Node<E> f = first; // 頭節(jié)點
node.next = f; // 當前節(jié)點放到頭節(jié)點,成為新的頭節(jié)點
first = node;
if (last == null)
last = node; // 如果沒有尾節(jié)點丧裁,則也置為尾節(jié)點
else
f.prev = node;
++count;
notEmpty.signal(); // 喚醒出隊操作护桦,代表現在隊列不是空
return true;
}
上面的函數比較簡單,就做了兩件事:
- 重置頭結點
- 喚醒出隊線程煎娇,因為是一個入隊操作二庵,那么至少隊列的數量有一個,也就是不可能為空了缓呛,表示現在可以出隊了催享。
linkLast也是一樣,其實由于前面已經加鎖了哟绊,不會產生并發(fā)因妙,所以邏輯非常簡單,就是雙向隊列的入隊操作票髓。
出隊操作
與入隊對應攀涵,出隊操作也有三種,remove炬称、poll汁果、take。玲躯。
看一下三者的區(qū)別:
public E remove() {
return removeFirst();
}
public E removeLast() {
E x = pollLast(); // 其實就是調用的poll
if (x == null) throw new NoSuchElementException(); 只不過為空的時候會拋錯
return x;
}
public E pollLast() {
final ReentrantLock lock = this.lock;
lock.lock(); // 采用獨占鎖進行同步
try {
return unlinkLast();
} 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();
}
}
可以看出据德,與入隊幾乎一樣,兩點不同:
- take中加個while循環(huán)跷车,如果失敗棘利,則進行等待,只有unlink成功的條件才往下執(zhí)行朽缴,而poll直接返回unlink的結果
- remove函數在失敗后會拋錯NoSuchElementException的異常
其核心函數還是unlink :
private E unlinkLast() {
// assert lock.isHeldByCurrentThread();
Node<E> l = last;
if (l == null)
return null;
Node<E> p = l.prev;
E item = l.item; // 保存值用來返回
l.item = null;
l.prev = l; // 指向自己延后釋放
last = p; // 重新指定隊尾
if (p == null)
first = null; // 如果隊列為空就沒必要再指定下一個節(jié)點了
else
p.next = null;
--count;
notFull.signal(); // 至少有一個空余善玫,喚醒某個入隊線程,使其加入同步隊列
return item;
}
上面的函數比較簡單密强,就做了兩件事:
- 釋放尾節(jié)點茅郎、并充值last節(jié)點
- 喚醒入隊線程蜗元,因為是一個出隊操作,那么至少隊列的數量減一系冗,表示現在可以入隊了奕扣。
獲取元素
該操作與出列非常相似,只是沒有移除的步驟掌敬,獲取操作有兩種:get惯豆、和peek
public E getFirst() {
E x = peekFirst();
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();
}
}
可以看出唯一的區(qū)別就是為空時拋不拋異常。