文章目錄
1.節點:Node
item保存當前節點的正式的屬性值,所以他是一個泛型標記的
static class Node<E> {
//當前節點的真正屬性值保存在這邊
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
2.構造函數
默認構造函數的容量是Integer.MAX_VALUE
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
3.重要參數說明
1.隊列容量:capacity
最大容量
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;
2.隊列當前數量:count
當前的容量
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();
3.控制 take,poll的鎖:takeLock
/** Lock held by take, poll, etc */
//控制 take,poll的鎖
private final ReentrantLock takeLock = new ReentrantLock();
4.控制put ,offer方法的鎖:putLock
/** Lock held by put, offer, etc */
//控制put ,offer方法的鎖
private final ReentrantLock putLock = new ReentrantLock();
5.線程隊列(等待移除出隊列的操作):notEmpty
注意這個Condition是從lock中來的,lock鎖定的是當前的隊列的操作,而這邊的notEmpty表示的就是當前的某一種隊列操作的線程:
比如說這邊根據上面的takeLock可以知道,他其實對應的是take和poll操作的線程
/** Wait queue for waiting takes */
// 等待被獲取(take)的隊列
private final Condition notEmpty = takeLock.newCondition();
6.線程隊列(等待添加到隊列的操作):notFull
注意這個Condition是從lock中來的,lock鎖定的是當前的隊列的操作,而這邊的notFull表示的就是當前的某一種隊列操作的線程:
比如說這邊根據上面的putLock可以知道,他其實對應的是put 和offer操作的線程
/** Wait queue for waiting puts */
//等待被添加(put)的隊列
private final Condition notFull = putLock.newCondition();
4.出隊列:poll
這邊需要注意的是:
getAndDecrement這個方法返回的是修改前的值,這個沒有理解搞得我有點懵逼
然後這邊會喚醒下一個需要 take、poll 操作的線程
並且在最後判斷修改前的隊列數量是否等於最大容量(capital),判斷是否喚醒阻塞中的put、offer的操作線程:notFull,因爲這邊是加鎖的,所以不需要當前線程的併發可能導致的信息丟失問題。
public E poll() {
final AtomicInteger count = this.count;
//如果當前的節點數量爲0,直接返回null
if (count.get() == 0) {
return null;
}
E x = null;
int c = -1;
//拿到take鎖
final ReentrantLock takeLock = this.takeLock;
//鎖定
takeLock.lock();
try {
if (count.get() > 0) {
//拿到第一個節點對應的元素值
x = dequeue();
//總數 - 1 (這邊是lock了,所以這邊cas一定會成功)
c = count.getAndDecrement();
//如果當前總數大於1
if (c > 1) {
//喚醒下一個需要移除隊列節點操作(take、poll)的線程.
notEmpty.signal();
}
}
} finally {
//最終解鎖
takeLock.unlock();
}
//the previous value,因爲上面的getAndDecrement返回的是之前的值,所以如果上一次的數量等於容量,那麼減一之後就需要喚醒需要添加到隊列(put、offer)操作的線程.
if (c == capacity) {
signalNotFull();
}
//返回元素
return x;
}
1.移除頭結點:dequeue
這邊有一個移除頭節點的操作
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
//拿到第一個節點
Node<E> h = head;
Node<E> first = h.next;
// help GC
h.next = h;
//第一個節點出隊列
head = first;
E x = first.item;
first.item = null;
//返回節點包含的元素
return x;
}
2.喚醒下一個節點:signal
喚醒下一個節點的操作其實相當於是把在等待的條件隊列中的節點添加到同步隊列.
public final void signal() {
//如果線程不等於當前持鎖線程,報錯
if (!isHeldExclusively()) {
throw new IllegalMonitorStateException();
}
Node first = firstWaiter;
//如果第一個節點不爲空,從第一個節點開始進行喚醒操作
if (first != null) {
doSignal(first);
}
}
private void doSignal(Node first) {
do {
//沒有節點的情況
if ((firstWaiter = first.nextWaiter) == null){
lastWaiter = null;
}
first.nextWaiter = null;
//循環退出的條件:1.節點加入到同步隊列,2.沒有對應節點
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
5.添加節點:put
put這邊會根據當前容量和最大容量的比較,決定操作線程是掛起等待還是正常執行,這邊通過加putLock保證高併發不互相干擾
public void put(E e) throws InterruptedException {
//節點如果爲null報錯
if (e == null) {
throw new NullPointerException();
}
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//鎖住(中斷會導致報錯)
putLock.lockInterruptibly();
try {
//如果count數量等於隊列容量,說明操作隊列已經滿了,只能添加到阻塞隊列中等待,在await中的操作會被park,等待喚醒
while (count.get() == capacity) {
//添加到條件隊列等待
notFull.await();
}
//如果總數不等於容量往隊列中添加節點
enqueue(node);
//增加當前線程數量
c = count.getAndIncrement();
//節點數量 < 容量 - 1,也就是說下一次入隊還不會達到隊列的最大容量
if (c + 1 < capacity) {
//從條件隊列中喚醒,添加到同步隊列
notFull.signal();
}
} finally {
//最終解鎖
putLock.unlock();
}
//如果當前數量小於0,喚醒下一個節點不爲空的節點
if (c == 0) {
signalNotEmpty();
}
}
6.刪除節點:remove
刪除操作會把putLock和takeLock都拿到,在刪除的時候不允許其他 操作線程執行他們的操作
public boolean remove(Object o) {
//如果節點爲空,則移除失敗
if (o == null) {
return false;
}
//要把put和take都鎖住
fullyLock();
try {
//遍歷每一個節點,如果節點的值和當前要刪除的值對應的上,則取消關聯(刪除)
for (Node<E> trail = head, p = trail.next; p != null; trail = p, p = p.next) {
if (o.equals(p.item)) {
//取消關聯
unlink(p, trail);
return true;
}
}
return false;
} finally {
//最終兩個都解鎖
fullyUnlock();
}
}
void unlink(Node<E> p, Node<E> trail) {
p.item = null;
trail.next = p.next;
if (last == p) {
last = trail;
}
//如果前一次操作的容量和最大容量一樣,說明刪除一個操作線程之後,可以把要下一個 添加操作的線程喚醒添加到同步隊列
if (count.getAndDecrement() == capacity) {
notFull.signal();
}
}
7.拿到隊頭節點但不出隊:peek
這邊可以看到,因爲peek沒有改變隊列,所以不會操作notEmpty、notFull兩個隊列
public E peek() {
if (count.get() == 0){
//如果總數爲0,則返回null
return null;
}
//鎖住take鎖
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
8.添加節點,容量滿直接廢棄:offer
這個方法和put類似,但是如果當前隊列已經達到最大容量,則會直接廢棄,返回false,這個方法有一個重載方法會記錄時間,當時間到達的時候如果對隊列還是達到最大容量才進行放棄;
public boolean offer(E e) {
if (e == null) {
throw new NullPointerException();
}
final AtomicInteger count = this.count;
//這邊如果當前的數量大於最大容量,則直接返回false。否則後面的操作和put類似
if (count.get() == capacity){
return false;
}
//下面的操作和put類似
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
9.總結
這邊的兩個鎖對應兩個部分的操作,拿出操作和放入操作,並且維護兩個ConditionObject對象,表示掛起等待的拿出操作的線程隊列和掛起等待的放入操作的線程隊列。
那麼就很好理解了,如果當前的數量count = 0,可以放入(如果有的話要喚醒),並且不能拿。
如果當前數量爲最大容量-1,則要把掛起等待的放入操作的線程喚醒,往同步隊列中添加放入操作線程