文章目录
BlockingQueue 阻塞队列
一、BlockingQueue 接口
1、概述
BlockingQueue
代表了一个线程安全的阻塞队列,不仅可以由多个线程并发访问,还提供了可阻塞的插入和移除方法。
- 当队列已满,向队列中
put
添加元素时(生产)会被阻塞,直到队列有足够的空间。 - 当队列为空,从队列中
take
移除元素时(消费)会被阻塞,直到队列有元素可用。
因此,BlockingQueue
被广泛用于“生产者-消费者”问题中。
2、接口方法
BlockingQueue
接口继承于 Queue
接口
public interface Queue<E> extends Collection<E> {
// 将指定的元素插入到队列中,如果队列已满,则抛出 IllegalStateException 异常。
boolean add(E e);
// 将指定的元素插入到队列中,如果队列已满,则返回 false,否则返回 true。
boolean offer(E e);
// 移除并返回队列的头部元素,如果队列为空,则抛出 NoSuchElementException 异常。
E remove();
// 移除并返回队列的头部元素,如果队列为空,则返回 null。
E poll();
// 获取但不移除队列的头部元素,如果队列为空,则抛出 NoSuchElementException 异常。
E element();
// 获取但不移除队列的头部元素,如果队列为空,则返回 null。
E peek();
}
BlockingQueue
接口还扩展了以下方法(可以看到有阻塞的插入和移除方法)
public interface BlockingQueue<E> extends Queue<E> {
// 将指定的元素插入到队列中,如果队列已满,则阻塞等待,直到队列有足够的空间。
void put(E e) throws InterruptedException;
// 移除并返回队列的头部元素,如果队列为空,则阻塞等待,直到队列有元素可用。
E take() throws InterruptedException;
// 带超时时间的offer
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
// 带超时时间的poll
E poll(long timeout, TimeUnit unit) throws InterruptedException;
// 从阻塞队列中移除并返回指定数量的元素,并将它们添加到给定的集合中。
int drainTo(Collection<? super E> c);
// maxElements:要移除的最大元素数量。
int drainTo(Collection<? super E> c, int maxElements);
// 返回当前阻塞队列中剩余的可用容量,即队列中可以插入的元素数量。
int remainingCapacity();
}
3、小结
总结一下 BlockingQueue
主要的几个方法:
方式 | 抛出异常 | 不抛异常,返回false或null | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add(E e) |
offer(E e) |
put(E e) |
offer(e, timeout, unit) |
移除并返回首个 | remove() |
poll() |
take() |
poll(timeout, unit) |
获取不移除首个 | element() |
peek() |
— | — |
二、ArrayBlockingQueue
1、概述
ArrayBlockingQueue
是一个基于数组的有界阻塞队列:
- 有界:在构造时就确定了容量大小,并且在之后不能更改。这个界限提供了流量控制,有助于资源的合理使用。
- FIFO:队列操作符合先进先出的原则。
- 公平:构造方法提供的
boolean fair
参数,可以指定是否公平。
2、基本实现
// java.util.concurrent.ArrayBlockingQueue
/** The queued items */
final Object[] items;
/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** 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();
}
3、put 方法详解
// java.util.concurrent.ArrayBlockingQueue
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果当前队列已满,将线程移入到notFull等待队列中
while (count == items.length)
notFull.await();
// 满足插入数据的要求,直接进行入队操作
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
// 插入数据
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
// 唤醒阻塞的消费者线程,当前队列中有数据可供消费
notEmpty.signal();
}
4、take 方法详解
// java.util.concurrent.ArrayBlockingQueue
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果当前队列为空,将线程移入到notEmpty等待队列中
while (count == 0)
notEmpty.await();
// 获取数据
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
// 获取数据
E x = (E) item