天天看點

Java 經典面試題:聊一聊 JUC 下的 LinkedBlockingQueue

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