阻塞隊列(BlockingQueue)源碼分析

前言

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();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章