【圖解JDK源碼】BlockingQueue的基本原理

1. 前言

BlockingQueue即阻塞隊列,它算是一種將ReentrantLock用得非常精彩的一種表現,依據它的基本原理,我們可以實現Web中的長連接聊天功能,當然其最常用的還是用於實現生產者與消費者模式,大致如下圖所示:

生產者與消費者模式

在Java中,BlockingQueue是一個接口,它的實現類有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等,它們的區別主要體現在存儲結構上或對元素操作上的不同,但是對於take與put操作的原理,卻是類似的。下面的源碼以ArrayBlockingQueue爲例。

2. 分析

BlockingQueue內部有一個ReentrantLock,其生成了兩個Condition,在ArrayBlockingQueue的屬性聲明中可以看見:

/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;

...

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

而如果能把notEmpty、notFull、put線程、take線程擬人的話,那麼我想put與take操作可能會是下面這種流程:

put(e)
put(e)

take()
take()

其中ArrayBlockingQueue.put(E e)源碼如下(其中中文註釋爲自定義註釋,下同):

/**
 * Inserts the specified element at the tail of this queue, waiting
 * for space to become available if the queue is full.
 *
 * @throws InterruptedException {@inheritDoc}
 * @throws NullPointerException {@inheritDoc}
 */
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await(); // 如果隊列已滿,則等待
        insert(e);
    } finally {
        lock.unlock();
    }
}

/**
 * Inserts element at current put position, advances, and signals.
 * Call only when holding lock.
 */
private void insert(E x) {
    items[putIndex] = x;
    putIndex = inc(putIndex);
    ++count;
    notEmpty.signal(); // 有新的元素被插入,通知等待中的取走元素線程
}

ArrayBlockingQueue.take()源碼如下:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await(); // 如果隊列爲空,則等待
        return extract();
    } finally {
        lock.unlock();
    }
}

/**
 * Extracts element at current take position, advances, and signals.
 * Call only when holding lock.
 */
private E extract() {
    final Object[] items = this.items;
    E x = this.<E>cast(items[takeIndex]);
    items[takeIndex] = null;
    takeIndex = inc(takeIndex);
    --count;
    notFull.signal(); // 有新的元素被取走,通知等待中的插入元素線程
    return x;
}

可以看見,put(E)與take()是同步的,在put操作中,當隊列滿了,會阻塞put操作,直到隊列中有空閒的位置。而在take操作中,當隊列爲空時,會阻塞take操作,直到隊列中有新的元素。

而這裏使用兩個Condition,則可以避免調用signal()時,會喚醒相同的put或take操作。

以上。

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