阻塞队列不仅可以作为容器,更重要的是可以作为线程同步的工具。LinkedBlockingQueue是BlockingQueue接口的实现类。因此LinkedBlockingQueue具有BlockingQueue的特征:当生产者线程试图向LinkedBlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从LinkedBlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。接下来从几个方面对LinkedBlockingQueue进行讲解。
1.参数及构造函数
/** 容器的容量 */
private final int capacity;
/** 当前元素数量,使用AtomicInteger保证原子性 */
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;
/** 可重入锁,用于take和poll的使用时加锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 不空时获取 */
private final Condition notEmpty = takeLock.newCondition();
/** 用于插入元素加锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 不满时插入 */
private final Condition notFull = putLock.newCondition();
/**
以上首先保证元素数量的统计的原子性,使用了JUC包下的AtomicInteger类。另外为了保证队列空和满与元素获取以及插入分别分别引入了可重入锁以及通信条件。
2.主要方法
LinkedBlockingQueue的主要方法有put(E e)、take()等,下面分别介绍它们在阻塞队列中是如何工作的。
首先是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 {
/**
* 当元素数量等于容量时,不允许插入,因此使插入动作进行等待
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
/**
*
*/
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
对元素进行插入,如果元素数量等于容量,则让插入操作进行等待,如果阻塞队列一旦从空到有元素,则调用signalNotEmpty()方法,其中该方法主要为了唤醒一些因空而等待的线程。
同理,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();
}
if (c == capacity)
signalNotFull();
return x;
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
take()则是消费者线程消费元素时,则需要判断是否为空,如果为空则线程等待,如果不为空,则获取元素,并对当前元素数目进行原子操作减,如果当前元素数目等于容量,那么调用该方法后,则可以激活那些因满而等待的线程。