阻塞队列之四:ArrayBlockingQueue
一、ArrayBlockingQueue简介
一个由循环数组支持的有界阻塞队列。它的本质是一个基于数组的BlockingQueue的实现。 它的容纳大小是固定的。此队列按 FIFO(先进先出)原则对元素进行排序。 队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。
新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素。 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。 一旦创建了这样的缓存区,就不能再增加其容量。
- 试图向已满队列中放入元素会导致放入操作受阻塞,直到BlockingQueue里有新的唤空间才会被醒继续操作;
- 试图从空队列中检索元素将导致类似阻塞,直到BlocingkQueue进了新货才会被唤醒。
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。 默认情况下,不保证是这种排序。然而,通过在构造函数将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。 公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
注意1:它是有界阻塞队列。它是数组实现的,是一个典型的“有界缓存区”。数组大小在构造函数指定,而且从此以后不可改变。
注意2:是它线程安全的,是阻塞的,具体参考BlockingQueue的“注意4”。
注意3:不接受 null 元素
注意4:公平性 (fairness)可以在构造函数中指定。
见下面是ArrayBlockingQueue的几种构造函数:
public ArrayBlockingQueue(int capacity) public ArrayBlockingQueue(int capacity, boolean fair) public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
fair如果为true,则按照 FIFO 顺序访问插入或移除时受阻塞线程的队列;如果为 false,则访问顺序是不确定的。
注意5:它实现了BlockingQueue接口。
注意6:此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选方法。
注意7:其容量在构造函数中指定。容量不可以自动扩展,也没提供手动扩展的接口。
注意8:在JDK5/6中,LinkedBlockingQueue和ArrayBlocingQueue等对象的poll(long timeout, TimeUnit unit)存在内存泄露
据称JDK5会在Update12里Fix,JDK6会在Update2里Fix。
二、ArrayBlockingQueue源码分析
一个基本数组的阻塞队列。可以设置列队的大小。
它的基本原理实际还是数组,只不过存、取、删时都要做队列是否满或空的判断。然后加锁访问。
2.1、ArrayBlockingQueue的lock
首先,成员变量有一个Lock和两个Condition的定义及初始化过程如下:
ArrayBlockingQueue的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制(classic two-condition algorithm)。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(); }
说明:fair是“可重入的独占锁(ReentrantLock)”的类型。fair为true,表示是公平锁;fair为false,表示是非公平锁。notEmpty和notFull是锁的两个Condition条件。
Lock的作用是提供独占锁机制,来保护竞争的资源;而Condition是为了更精细的对锁进行控制,但是依赖于lock,通过某个条件对多线程进行控制。ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。
notEmpty表示"锁的非空条件"。当某线程想从队列中获取数据的时候,而此时队列中的数据为空,则该线程通过notEmpty.await()方法进行等待;当其他线程向队列中插入元素之后,就调用notEmpty.signal()方法进行唤醒之前等待的线程。
同理,notFull表示“锁满的条件“。当某个线程向队列中插入元素,而此时队列已满时,该线程等待,即阻塞通过notFull.wait()方法;其他线程从队列中取出元素之后,就唤醒该等待的线程,这个线程调用notFull.signal()方法。
ArrayBlockingQueue使用ReentrantLock来实现的添加元素原子操作,具体可以看poll和offer中的阻塞awaitNanos(nanos)是使用了Condition中的AQS中的一个方法。
2.2、入队
增加元素有多种方法add(),offer(),put(),其中add()方法调用offer()方法。所以只用关注offer()和put()方法即可,offer()和put()不同点是,offer()在队列满时,直接返回false,不会阻塞写入线程。而put()在队列满时,会一直阻塞写入线程,直到有存储空间可以存放元素。
/** * 添加一个元素,其实super.add里面调用了offer方法 */ public boolean add(E e) { return super.add(e); } /** *加入成功返回true,否则返回false * */ 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(); } } /** * 如果队列已满的话,就会等待 */ public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly();//和lock()方法的区别是让它在阻塞时也可抛出异常跳出 try { while (count == items.length) notFull.await(); //这里就是阻塞了,要注意。如果运行到这里,那么它会释放上面的锁,一直等到notify enqueue(e); } finally { lock.unlock(); } }
enqueue()方法是最终增加元素的方法:
/** * 元素放入队列,注意调用这个方法时都要先加锁 * */ private void enqueue(E x) { final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) //循环队列,计算下标 putIndex = 0; count++;//当前拥有元素个数加1 notEmpty.signal();//有一个元素加入成功,那肯定队列不为空 }
这里由于在操作共享变量前加了锁,所以不存在内存不可见问题,加过锁后获取的共享变量都是从主内存获取的,而不是在CPU缓存或者寄存器里面的值,释放锁后修改的共享变量值会刷新会主内存中。
另外这个队列是使用循环数组实现,所以计算下一个元素存放下标时候有些特殊。另外offer后调用 notEmpty.signal();是为了激活调用notEmpty.await()阻塞后放入notEmpty条件队列中的线程。
2.3、出队
对称的取出元素的方法有:poll(),take(),remove()方法。poll()取出元素时,当队列为空时,返回null,不会阻塞取数线程;而take()方法,当队列为空时,会阻塞取数线程,直到有元素返回为止。
//实现的方法,如果当前队列为空,返回null public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } //实现的方法,如果当前队列为空,一直阻塞 public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await();//队列为空,阻塞方法 return dequeue(); } finally { lock.unlock(); } } //带有超时时间的取元素方法,否则返回Null public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) { if (nanos <= 0) return null; nanos = notEmpty.awaitNanos(nanos);//超时等待 } return dequeue();//取得元素 } finally { lock.unlock(); } }
remove()方法:
public boolean remove(Object o) { if (o == null) return false; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { if (count > 0) { final int putIndex = this.putIndex; int i = takeIndex; do { if (o.equals(items[i])) { removeAt(i); return true; } if (++i == items.length) i = 0; } while (i != putIndex); } return false; } finally { lock.unlock(); } }
真正出队的方法是dequeue()方法:
/** * 元素出队,注意调用这个方法时都要先加锁 * */ private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--;//当前拥有元素个数减1 if (itrs != null) itrs.elementDequeued(); notFull.signal();//有一个元素取出成功,那肯定队列不满 return x; }
2.3、完整源码
package java.util.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.AbstractQueue; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.lang.ref.WeakReference; import java.util.Spliterators; import java.util.Spliterator; public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { private static final long serialVersionUID = -817911632652898426L; /** 真正存入数据的数组*/ final Object[] items; /** take, poll, peek or remove的下一个索引 */ int takeIndex; /** put, offer, or add的下一个索引 */ int putIndex; /**队列中元素个数*/ int count; /**可重入锁 */ final ReentrantLock lock; /** 队列不为空的条件 */ private final Condition notEmpty; /** 队列未满的条件 */ private final Condition notFull; transient Itrs itrs = null; /** *当前元素个数-1 */ final int dec(int i) { return ((i == 0) ? items.length : i) - 1; } /** * 返回对应索引上的元素 */ @SuppressWarnings("unchecked") final E itemAt(int i) { return (E) items[i]; } /** * 非空检查 * * @param v the element */ private static void checkNotNull(Object v) { if (v == null) throw new NullPointerException(); } /** * 元素放入队列,注意调用这个方法时都要先加锁 * */ private void enqueue(E x) { final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++;//当前拥有元素个数加1 notEmpty.signal();//有一个元素加入成功,那肯定队列不为空 } /** * 元素出队,注意调用这个方法时都要先加锁 * */ private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--;//当前拥有元素个数减1 if (itrs != null) itrs.elementDequeued(); notFull.signal();//有一个元素取出成功,那肯定队列不满 return x; } /** * 指定删除索引上的元素 * */ void removeAt(final int removeIndex) { final Object[] items = this.items; if (removeIndex == takeIndex) { items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); } else { final int putIndex = this.putIndex; for (int i = removeIndex;;) { int next = i + 1; if (next == items.length) next = 0; if (next != putIndex) { items[i] = items[next]; i = next; } else { items[i] = null; this.putIndex = i; break; } } count--; if (itrs != null) itrs.removedAt(removeIndex); } notFull.signal();//有一个元素删除成功,那肯定队列不满 } /** * * 构造函数,设置队列的初始容量 */ public ArrayBlockingQueue(int capacity) { this(capacity, false); } /** * 构造函数。capacity设置数组大小 ,fair设置是否为公平锁 * capacity and the specified access policy. */ public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair);//是否为公平锁,如果是的话,那么先到的线程先获得锁对象。 //否则,由操作系统调度由哪个线程获得锁,一般为false,性能会比较高 notEmpty = lock.newCondition(); notFull = lock.newCondition(); } /** *构造函数,带有初始内容的队列 */ public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { this(capacity, fair); final ReentrantLock lock = this.lock; lock.lock(); //要给数组设置内容,先上锁 try { int i = 0; try { for (E e : c) { checkNotNull(e); items[i++] = e;//依次拷贝内容 } } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalArgumentException(); } count = i; putIndex = (i == capacity) ? 0 : i;//如果putIndex大于数组大小 ,那么从0重新开始 } finally { lock.unlock();//最后一定要释放锁 } } /** * 添加一个元素,其实super.add里面调用了offer方法 */ public boolean add(E e) { return super.add(e); } /** *加入成功返回true,否则返回false * */ 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(); } } /** * 如果队列已满的话,就会等待 */ public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly();//和lock()方法的区别是让它在阻塞时也可抛出异常跳出 try { while (count == items.length) notFull.await(); //这里就是阻塞了,要注意。如果运行到这里,那么它会释放上面的锁,一直等到notify enqueue(e); } finally { lock.unlock(); } } /** * 带有超时时间的插入方法,unit表示是按秒、分、时哪一种 */ public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { checkNotNull(e); long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) { if (nanos <= 0) return false; nanos = notFull.awaitNanos(nanos);//带有超时等待的阻塞方法 } enqueue(e);//入队 return true; } finally { lock.unlock(); } } //实现的方法,如果当前队列为空,返回null public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } //实现的方法,如果当前队列为空,一直阻塞 public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await();//队列为空,阻塞方法 return dequeue(); } finally { lock.unlock(); } } //带有超时时间的取元素方法,否则返回Null public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) { if (nanos <= 0) return null; nanos = notEmpty.awaitNanos(nanos);//超时等待 } return dequeue();//取得元素 } finally { lock.unlock(); } } //只是看一个队列最前面的元素,取出是不删除队列中的原来元素。队列为空时返回null public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return itemAt(takeIndex); // 队列为空时返回null } finally { lock.unlock(); } } /** * 返回队列当前元素个数 * */ public int size() { final ReentrantLock lock = this.lock; lock.lock(); try { return count; } finally { lock.unlock(); } } /** * 返回当前队列再放入多少个元素就满队 */ public int remainingCapacity() { final ReentrantLock lock = this.lock; lock.lock(); try { return items.length - count; } finally { lock.unlock(); } } /** * 从队列中删除一个元素的方法。删除成功返回true,否则返回false */ public boolean remove(Object o) { if (o == null) return false; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { if (count > 0) { final int putIndex = this.putIndex; int i = takeIndex; do { if (o.equals(items[i])) { removeAt(i); //真正删除的方法 return true; } if (++i == items.length) i = 0; } while (i != putIndex);//一直不断的循环取出来做判断 } return false; } finally { lock.unlock(); } } /** * 是否包含一个元素 */ public boolean contains(Object o) { if (o == null) return false; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { if (count > 0) { final int putIndex = this.putIndex; int i = takeIndex; do { if (o.equals(items[i])) return true; if (++i == items.length) i = 0; } while (i != putIndex); } return false; } finally { lock.unlock(); } } /** * 清空队列 * */ public void clear() { final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { int k = count; if (k > 0) { final int putIndex = this.putIndex; int i = takeIndex; do { items[i] = null; if (++i == items.length) i = 0; } while (i != putIndex); takeIndex = putIndex; count = 0; if (itrs != null) itrs.queueIsEmpty(); for (; k > 0 && lock.hasWaiters(notFull); k--) notFull.signal(); } } finally { lock.unlock(); } } /** * 取出所有元素到集合 */ public int drainTo(Collection<? super E> c) { return drainTo(c, Integer.MAX_VALUE); } /** * 取出所有元素到集合 */ public int drainTo(Collection<? super E> c, int maxElements) { checkNotNull(c); if (c == this) throw new IllegalArgumentException(); if (maxElements <= 0) return 0; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { int n = Math.min(maxElements, count); int take = takeIndex; int i = 0; try { while (i < n) { @SuppressWarnings("unchecked") E x = (E) items[take]; c.add(x); items[take] = null; if (++take == items.length) take = 0; i++; } return n; } finally { // Restore invariants even if c.add() threw if (i > 0) { count -= i; takeIndex = take; if (itrs != null) { if (count == 0) itrs.queueIsEmpty(); else if (i > take) itrs.takeIndexWrapped(); } for (; i > 0 && lock.hasWaiters(notFull); i--) notFull.signal(); } } } finally { lock.unlock(); } } }
2.4、size
public int size() { final ReentrantLock lock = this.lock; lock.lock(); try { return count; } finally { lock.unlock(); } }
2.4、peek方法
返回队列头元素但不移除该元素,队列为空,返回null
public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return itemAt(takeIndex); // null when queue is empty } finally { lock.unlock(); } }
三、JDK或开源框架中使用
四、使用示例
4.1、生产者-消费者模型
package com.dxz.queue.array; import java.util.concurrent.ArrayBlockingQueue; public class Producer implements Runnable{ //容器 private final ArrayBlockingQueue<Bread> queue; public Producer(ArrayBlockingQueue<Bread> queue){ this.queue = queue; } @Override public void run() { while(true){ produce(); } } public void produce(){ /** * put()方法是如果容器满了的话就会把当前线程挂起 * offer()方法是容器如果满的话就会返回false。 */ try { Bread bread = new Bread(); bread.setName(System.currentTimeMillis()+""); queue.put(bread); System.out.println("Producer:"+bread); } catch (InterruptedException e) { e.printStackTrace(); } } } package com.dxz.queue.array; import java.util.concurrent.ArrayBlockingQueue; public class Consumer implements Runnable{ //容器 private final ArrayBlockingQueue<Bread> queue; public Consumer(ArrayBlockingQueue<Bread> queue){ this.queue = queue; } @Override public void run() { while(true){ consume(); } } public void consume(){ /** * take()方法和put()方法是对应的,从中拿一个数据,如果拿不到线程挂起 * poll()方法和offer()方法是对应的,从中拿一个数据,如果没有直接返回null */ try { Bread bread = queue.take(); //Bread bread = queue.poll(); System.out.println("consumer:"+bread); } catch (Exception e) { e.printStackTrace(); } } } package com.dxz.queue.array; import java.util.concurrent.ArrayBlockingQueue; public class Client { public static void main(String[] args) { int capacity = 10; ArrayBlockingQueue<Bread> queue = new ArrayBlockingQueue<Bread>(capacity); new Thread(new Producer(queue)).start(); new Thread(new Producer(queue)).start(); new Thread(new Consumer(queue)).start(); new Thread(new Consumer(queue)).start(); new Thread(new Consumer(queue)).start(); } }
cosumer中用take()方法的其中一次结果如下:
... Producer:Bread [name=1500516869536] Producer:Bread [name=1500516869536] Producer:Bread [name=1500516869536] consumer:Bread [name=1500516869536] consumer:Bread [name=1500516869536] ...
cosumer中用poll()方法的其中一次结果如下:
... consumer:Bread [name=1500517024449] consumer:null consumer:null consumer:null consumer:null Producer:Bread [name=1500517024449] Producer:Bread [name=1500517024449] ...