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 隊列的簡單源碼解析,希望對你的面試或者工作有所幫助,感謝你的閱讀~
歡迎關注公眾號【互聯網平頭哥】,一起成長,一起進步~。