ArrayBlockingQueue
ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列,常用于生产者-消费者模式,能够容纳指定个数的元素,当队列满时不能再放新的元素,当队列为空时不能取出元素,此时相应的生产者或者消费者线程往往会挂起。本类是一种先进先出(FIFO)的队列结构,内部通过数组实现。
注意:ArrayBlockingQueue不同于ConcurrentLinkedQueue,ArrayBlockingQueue是数组实现的,并且是有界限的;而ConcurrentLinkedQueue是链表实现的,是无界限的
1. ArrayBlockingQueue继承于AbstractQueue,并且它实现BlockingQueue接口
2. ArrayBlockingQueue内部是通过Object[]数组保存数据的,ArrayBlockingQueue的大小,即数组的容量是创建ArrayBlockingQueue时指定的。
3. ArrayBlockingQueue与ReentrantLock是组合关系,ArrayBlockingQueue中包含一个ReentrantLock对象(lock)。ReentrantLock是可重入的互斥锁,ArrayBlockingQueue就是根据该互斥锁实现“多线程对竞争资源的互斥访问”。而且,ReentrantLock分为公平锁和非公平锁,关于具体使用公平锁还是非公平锁,在创建ArrayBlockingQueue时可以指定;而且,ArrayBlockingQueue默认会使用非公平锁
4. ArrayBlockingQueue与Condition是组合关系,ArrayBlockingQueue中包含两个Condition对象(notEmpty和notFull)。而且,Condition又依赖于ArrayBlockingQueue而存在,通过Condition可以实现对ArrayBlockingQueue的更精确的访问 -- (01)若某线程(线程A)要取数据时,数组正好为空,则该线程会执行notEmpty.await()进行等待;当其它某个线程(线程B)向数组中插入了数据之后,会调用notEmpty.signal()唤醒“notEmpty上的等待线程”。此时,线程A会被唤醒从而得以继续运行。(02)若某线程(线程H)要插入数据时,数组已满,则该线程会它执行notFull.await()进行等待;当其它某个线程(线程I)取出数据之后,会调用notFull.signal()唤醒“notFull上的等待线程”。
ArrayBlockingQueue在生产者放入数据和消费者获取数据,是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。
BlockingQueue的常见接口:
add(E e) : 成功返回true,失败抛IllegalStateException异常 offer(E e) : 成功返回 true,如果此队列已满,则返回 false put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞 remove(Object o) :移除指定元素,成功返回true,失败抛出异常 poll() : 获取并移除此队列的头元素,若队列为空,则返回 null take():获取并移除此队列头元素,若没有元素则一直阻塞 element() :获取但不移除此队列的头元素,没有元素则抛异常 peek() :获取但不移除此队列的头;若队列为空,则返回 null
构造函数
/** 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; /** Main lock guarding all access */ final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull;
/** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity and the specified access policy. * * @param capacity the capacity of this queue * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. * @throws IllegalArgumentException if {@code capacity < 1} */ 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(); }
/** * Creates an {@code ArrayBlockingQueue} with the given (fixed) capacity and the specified access policy. * * @param capacity the capacity of this queue * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. * @throws IllegalArgumentException if {@code capacity < 1} */ 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(); }
ArrayBlockingQueue.put()
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(); enqueue(e); } finally { lock.unlock(); } }
put()方法的代码中,首先以可中断的方式获取锁,之后在谓词“缓存不满”上等待,如果队列满了,就调用notFull.await()挂起当前线程并释放锁,这里说的释放锁是await()方法带来的效果,不是指最后finally代码块中的unlock()。当缓存不满的条件满足时,会将元素放到缓存当中,并调用notEmpty.signal()方法唤醒一个消费者线程。
/** * Inserts the specified element at the tail of this queue if it is * possible to do so immediately without exceeding the queue's capacity, * returning {@code true} upon success and {@code false} if this queue * is full. This method is generally preferable to method {@link #add}, * which can fail to insert an element only by throwing an exception. * * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } }
//入队操作 private void enqueue(E x) { //获取当前数组 final Object[] items = this.items; //通过putIndex索引对数组进行赋值 items[putIndex] = x; //索引自增,如果已是最后一个位置,重新设置 putIndex = 0; if (++putIndex == items.length) putIndex = 0; count++;//队列中元素数量加1 //唤醒调用take()方法的线程,执行元素获取操作 notEmpty.signal(); }
ArrayBlockingQueue.take()
poll()获取并删除队列头元素,队列没有数据就返回null,内部通过dequeue()方法删除头元素
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { //判断队列是否为null,不为null执行dequeue()方法,否则返回null return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } }
take()方法是以阻塞的方式获取队列首部的元素,如果队列缓存非空就立即取出,否则挂起等待直到队列非空,这里的谓词是“缓存非空”,这是消费者调用的方法。该方法具体代码如下:
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } /** * Extracts element at current take position, advances, and signals. * Call only when holding lock. */ private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued();//同时更新迭代器中的元素数据 notFull.signal(); return x; }
take()方法首先以可中断的方式获取锁,之后在谓词“缓存非空”上等待,如果队列为空,就调用notEmpty.await()挂起当前线程并释放锁,当等待条件满足时,会从缓存中取出一个元素,并调用notFull.signal()唤醒一个生产者线程。
public boolean remove(Object o) { if (o == null) return false; //获取数组数据 final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock();//加锁 try { //如果此时队列不为null,这里是为了防止并发情况 if (count > 0) { //获取下一个要添加元素时的索引 final int putIndex = this.putIndex; //获取当前要被删除元素的索引 int i = takeIndex; //执行循环查找要删除的元素 do { //找到要删除的元素 if (o.equals(items[i])) { removeAt(i);//执行删除 return true;//删除成功返回true } //当前删除索引执行加1后判断是否与数组长度相等 //若为true,说明索引已到数组尽头,将i设置为0 if (++i == items.length) i = 0; } while (i != putIndex);//继承查找 } return false; } finally { lock.unlock(); } } //根据索引删除元素,实际上是把删除索引之后的元素往前移动一个位置 void removeAt(final int removeIndex) { final Object[] items = this.items; //先判断要删除的元素是否为当前队列头元素 if (removeIndex == takeIndex) { //如果是直接删除 items[takeIndex] = null; //当前队列头元素加1并判断是否与数组长度相等,若为true设置为0 if (++takeIndex == items.length) takeIndex = 0; count--;//队列元素减1 if (itrs != null) itrs.elementDequeued();//更新迭代器中的数据 } else { //如果要删除的元素不在队列头部, //那么只需循环迭代把删除元素后面的所有元素往前移动一个位置 //获取下一个要被添加的元素的索引,作为循环判断结束条件 final int putIndex = this.putIndex; //执行循环 for (int i = removeIndex;;) { //获取要删除节点索引的下一个索引 int next = i + 1; //判断是否已为数组长度,如果是从数组头部(索引为0)开始找 if (next == items.length) next = 0; //如果查找的索引不等于要添加元素的索引,说明元素可以再移动 if (next != putIndex) { items[i] = items[next];//把后一个元素前移覆盖要删除的元 i = next; } else { //在removeIndex索引之后的元素都往前移动完毕后清空最后一个元素 items[i] = null; this.putIndex = i; break;//结束循环 } } count--;//队列元素减1 if (itrs != null) itrs.removedAt(removeIndex);//更新迭代器数据 } notFull.signal();//唤醒添加线程 }
public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { //直接返回当前队列的头元素,但不删除 return itemAt(takeIndex); // null when queue is empty } finally { lock.unlock(); } } final E itemAt(int i) { return (E) items[i]; }
private class Itr implements Iterator<E> { // 队列中剩余元素的个数 private int remaining; // Number of elements yet to be returned // 下一次调用next()返回的元素的索引 private int nextIndex; // Index of element to be returned by next // 下一次调用next()返回的元素 private E nextItem; // Element to be returned by next call to next // 上一次调用next()返回的元素 private E lastItem; // Element returned by last call to next // 上一次调用next()返回的元素的索引 private int lastRet; // Index of last element returned, or -1 if none Itr() { // 获取“阻塞队列”的锁 final ReentrantLock lock = ArrayBlockingQueue.this.lock; lock.lock(); try { lastRet = -1; if ((remaining = count) > 0) nextItem = itemAt(nextIndex = takeIndex); } finally { // 释放“锁” lock.unlock(); } } public boolean hasNext() { return remaining > 0; } public E next() { // 获取“阻塞队列”的锁 final ReentrantLock lock = ArrayBlockingQueue.this.lock; lock.lock(); try { // 若“剩余元素<=0”,则抛出异常。 if (remaining <= 0) throw new NoSuchElementException(); lastRet = nextIndex; // 获取第nextIndex位置的元素 E x = itemAt(nextIndex); // check for fresher value if (x == null) { x = nextItem; // we are forced to report old value lastItem = null; // but ensure remove fails } else lastItem = x; while (--remaining > 0 && // skip over nulls (nextItem = itemAt(nextIndex = inc(nextIndex))) == null) ; return x; } finally { lock.unlock(); } } public void remove() { final ReentrantLock lock = ArrayBlockingQueue.this.lock; lock.lock(); try { int i = lastRet; if (i == -1) throw new IllegalStateException(); lastRet = -1; E x = lastItem; lastItem = null; // only remove if item still at index if (x != null && x == items[i]) { boolean removingHead = (i == takeIndex); removeAt(i); if (!removingHead) nextIndex = dec(nextIndex); } } finally { lock.unlock(); } } }

参考:
https://github.com/CarpenterLee/JCRecipes/blob/master/markdown/ArrayBlockingQueue.md
https://blog.csdn.net/javazejian/article/details/77410889(推荐)
http://www.cnblogs.com/skywang12345/p/3498652.html(推荐)
浙公网安备 33010602011771号