前言
GitHub:https://github.com/yihonglei/thinking-in-concurrent
一 BlockingQueue
BlockingQueue大概類關係。
BlockingQueue上層跟普通List一樣,遵循集合的標準規範。
LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。
ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。
DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
二 LinkedBlockingQueue
這裏分析一下LinkedBlockingQueue的存入元素和取出元素源碼,因爲線程池用得比較多。
其他隊列,點進去看看源碼,大同小異。
1、demo
先上個簡單實例,感受下什麼是阻塞。
package com.jpeony.concurrent.queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author yihonglei
*/
public class LinkedBlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
// 創建隊列
LinkedBlockingQueue blockingQueue = new LinkedBlockingQueue(10);
// 存入元素
blockingQueue.put("object01");
// 循環取元素
while (true) {
System.out.println("開始取值...");
// 當隊列沒有元素的時候,隊裏阻塞當前線程,直到隊列不爲空
System.out.println(blockingQueue.take());
System.out.println("取值完成!");
}
}
}
運行結果
程序說明
1)創建一個阻塞隊列,初始化大小爲10;
2)存入一個元素;
3)通過死循環去隊列取值,第一輪因爲隊列有元素,順利取到值,第二輪,因爲隊列沒有元素,
所以main線程被一直阻塞了,因爲這裏沒有別的地方放入元素,所以就一直阻塞住,這個就是阻塞的效果。
2、LinkedBlockingQueue構造器
1)構造器如果不傳入capacity大小,capacity默認是int的最大值;
2)初始化了鏈表的頭結點和尾結點;
3、put/offer(存儲元素)
offer非阻塞,隊裏滿了就放不進去,也不會等着空的時候再放;
put阻塞,當隊列滿了,一直等着啥時候空了,啥時候放,很執着;
BlockingQueue前置幾個屬性。
/** The capacity bound, or Integer.MAX_VALUE if none(集合最大容量) */
private final int capacity;
/** Current number of elements(當前集合元素個數) */
private final AtomicInteger count = new AtomicInteger();
/**
* Head of linked list.(頭結點)
* Invariant: head.item == null
*/
transient Node<E> head;
/**
* Tail of linked list.(尾節點)
* Invariant: last.next == null
*/
private transient Node<E> last;
/** Lock held by take, poll, etc(take時使用的重入鎖) */
private final ReentrantLock takeLock = new ReentrantLock()
/** Wait queue for waiting takes(隊列不爲空,通知等待獲取元素的線程獲取元素) */
private final Condition notEmpty = takeLock.newCondition()
/** Lock held by put, offer, etc (存入元素鎖)*/
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts(隊列不滿,通知等待存入數據的線程存入數據) */
private final Condition notFull = putLock.newCondition();
put的過程
public void put(E e) throws InterruptedException {
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 {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
// 如果隊列滿了,阻塞調用put的線程
while (count.get() == capacity) {
notFull.await();
}
// 入隊
enqueue(node);
c = count.getAndIncrement();
// 如果隊列不滿,喚醒調用put被阻塞的線程,存入元素
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// 當前隊列爲空
if (c == 0)
// 喚醒調用put被阻塞的線程,存入元素,是個保底方案
signalNotEmpty();
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
4、take/poll(獲取元素)
take 阻塞,隊列爲空時,線程阻塞,一直等到隊列有值獲取,也是很執着;
poll 非阻塞,隊列爲空時,直接退出;
take的過程。
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
// 如果隊列沒有元素,則一直等待,等待被喚醒
while (count.get() == 0) {
notEmpty.await();
}
// 從隊列取出值
x = dequeue();
c = count.getAndDecrement();
// 隊裏還有元素,則喚醒線程繼續獲取元素
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
// 喚醒等待put/poll的線程
if (c == capacity)
signalNotFull();
return x;
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}