Java 經典面試題:聊一聊 JUC 下的 LinkedBlockingQueue
本文聊一下 JUC 下的 LinkedBlockingQueue 隊列,先說說 LinkedBlockingQueue 隊列的特點,然後再從源碼的角度聊一聊 LinkedBlockingQueue 的主要實作~
LinkedBlockingQueue 有以下特點:
LinkedBlockingQueue 是阻塞隊列,底層是單連結清單實作的~
元素從隊列尾進隊,從隊列頭出隊,符合FIFO~
可以使用 Collection 和 Iterator 兩個接口的所有操作,因為實作了兩者的接口~
LinkedBlockingQueue 隊列讀寫操作都加了鎖,但是讀寫用的是兩把不同的鎖,是以可以同時讀寫操作~
LinkedBlockingQueue 隊列繼承了 AbstractQueue 類,實作了 BlockingQueue 接口,LinkedBlockingQueue 主要有以下接口:
//将指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量)
//在成功時傳回 true,如果此隊列已滿,則抛IllegalStateException。
boolean add(E e);
//将指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量)
// 将指定的元素插入此隊列的尾部,如果該隊列已滿,
//則在到達指定的等待時間之前等待可用的空間,該方法可中斷
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
//将指定的元素插入此隊列的尾部,如果該隊列已滿,則一直等到(阻塞)。
void put(E e) throws InterruptedException;
//擷取并移除此隊列的頭部,如果沒有元素則等待(阻塞),
//直到有元素将喚醒等待線程執行該操作
E take() throws InterruptedException;
//擷取并移除此隊列的頭,如果此隊列為空,則傳回 null。
E poll();
//擷取并移除此隊列的頭部,在指定的等待時間前一直等到擷取元素, //超過時間方法将結束
E poll(long timeout, TimeUnit unit) throws InterruptedException;
//從此隊列中移除指定元素的單個執行個體(如果存在)。
boolean remove(Object o);
//擷取但不移除此隊列的頭元素,沒有則跑異常NoSuchElementException
E element();
//擷取但不移除此隊列的頭;如果此隊列為空,則傳回 null。
E peek();
LinkedBlockingQueue 隊列的讀寫方法非常的多,但是常用的是 put()、take()方法,因為它們兩是阻塞的,是以我們就從源碼的角度來聊一聊 LinkedBlockingQueue 隊列中這兩個方法的實作。
先來看看 put()方法,源碼如下:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// 預先設定 c 的值為 -1,表示失敗
int c = -1;
Node<E> node = new Node<E>(e);
// 擷取寫鎖
final ReentrantLock putLock = this.putLock;
// 擷取目前隊列的大小
final AtomicInteger count = this.count;
// 設定可中斷鎖
putLock.lockInterruptibly();
try {
// 隊列滿了
// 目前線程阻塞,等待其他線程的喚醒(其他線程 take 成功後就會喚醒此處線程)
while (count.get() == capacity) {
// 無限期等待
notFull.await();
}
// 新增到隊列尾部
enqueue(node);
// 擷取目前的隊列數
c = count.getAndIncrement();
// 如果隊列未滿,嘗試喚醒一個put的等待線程
if (c + 1 < capacity)
notFull.signal();
} finally {
// 釋放鎖
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
put()方法的源碼并不難,非常容易就看懂,put()方法的過程大概如下:
1、先加鎖,保證容器的并發安全~
2、隊列新增資料,将資料追加到隊列尾部~
3、新增時,如果隊列滿了,目前線程是會被阻塞的,等待被喚醒~
4、新增資料成功後,在适當時機,會喚起 put 的等待線程(隊列不滿時),或者 take 的等待線程(隊列不為空時),這樣保證隊列一旦滿足 put 或者 take 條件時,立馬就能喚起阻塞線程,繼續運作,保證了喚起的時機不被浪費offer 就有兩兩種,一種是直接傳回 false,另一種是超過一定時間後傳回 false~
5、釋放鎖~
其他的新增方法,例如 offer,可以檢視源碼,跟put() 方法大同小異,相差不大~
再來看看 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比真實的隊列資料大一
c = count.getAndDecrement();
// c 大于 0 ,表示隊列有值,可以喚醒之前被阻塞的讀線程
if (c > 1)
notEmpty.signal();
} finally {
// 釋放鎖
takeLock.unlock();
}
// 隊列未滿,可以喚醒 put 等待線程~
if (c == capacity)
signalNotFull();
return x;
take()方法跟 put() 方法類似,是一個相反的操作,我就不做過多的說明了~
以上就是 LinkedBlockingQueue 隊列的簡單源碼解析,希望對你的面試或者工作有所幫助,感謝你的閱讀~
原文位址
https://www.cnblogs.com/jamaler/p/12849927.html