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)
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操作。
以上。