ArrayBlockingQueue原理介绍
ArrayBlockingQueue,是基于数组的阻塞队列,队列中的元素按照FIFO顺序。
创建ArrayBlockingQueue,是需要制定队列的容量的(不可省);指定队列容量后,会一次性创建capacity个长度的数组,用来存放队列元素;
需要注意的是,ArrayBlockingQueue使用的是循环数组来实现队列,也就是说,有takeIndex指向下一个出队元素,当takeIndex指向了capacity-1的位置(最后一个位置),那么元素出队后,下一次takeIndex会归零;同样的,对于putIndex指向存放下一个入队元素的位置,当putIndex指向了capacity-1的位置(最后一个位置),入队后,putIndex会归零,指向第一个节点。
另外,基于链表实现的阻塞队列LinkedBlockingQueue,可以参考:https://www.cnblogs.com/-beyond/p/14407364.html
原文地址:https://www.cnblogs.com/-beyond/p/14407201.html
属性字段
/** * 序列化版本id */ private static final long serialVersionUID = -817911632652898426L; /** * 保存队列元素的数组 */ final Object[] items; /** * 指向下一个元素的位置(进行take、poll、peek和remove),可以理解为队首指针 */ int takeIndex; /** * 指向下一个元素的位置(进行put、offer、add),可以理解为队尾指针 */ int putIndex; /** * 记录队列中中元素的位置 */ int count; /* * Concurrency control uses the classic two-condition algorithm * found in any textbook. */ /** * 访问控制的锁 */ final ReentrantLock lock; /** * 用于take阻塞的condition */ private final Condition notEmpty; /** * 用于put阻塞的condition */ private final Condition notFull; /** * Shared state for currently active iterators, or null if there * are known not to be any. Allows queue operations to update * iterator state. */ transient Itrs itrs = null;
构造方法
ArrayBlockingQueue提供了3个构造方法
/** * 初始化ArrayBlockingQueue,设置队列大小,使用非公平策略(默认) */ public ArrayBlockingQueue(int capacity) { this(capacity, false); } /** * 初始化ArrayBlockingQueue,设置队列大小,并设置是否使用公平策略 */ public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) { throw new IllegalArgumentException(); } // 创建n个元素的数组(赋值给队列数组) this.items = new Object[capacity]; // 创建可重复锁(使用设置的公平策略) lock = new ReentrantLock(fair); // 创建lock关联的notEmpty和notFull condition notEmpty = lock.newCondition(); notFull = lock.newCondition(); } /** * 初始化ArrayBlockingQueue,设置队列大小,并设置是否使用公平策略 * 然后将传入的元素加入到队列中(按照传入元素的顺序),元素不能为null */ public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { // 初始化队列 this(capacity, fair); // 获取锁,并加锁 final ReentrantLock lock = this.lock; lock.lock(); // Lock only for visibility, not mutual exclusion 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(队首) putIndex = (i == capacity) ? 0 : i; } finally { // 释放锁 lock.unlock(); } }
添加元素(入队)
ArrayBlockingQueue的入队分两大类,阻塞式入队和非阻塞式入队;
阻塞式入队是指,如果入队失败(队列已满),则会阻塞,知道成功入队,才会结束阻塞。
非阻塞式入队是指,不管入队是否成功,都会立即返回。
非阻塞式添加元素
非阻塞式添加元素,是指尝试元素入队时,不管是否成功,都立即返回;
对应的是add和offer两个方法,add调用的其实是offer。
/** * 将元素插入队列的尾部(入队) * 入队成功,返回true;入队失败,返回false */ public boolean offer(E e) { // 判断元素是否为null,如果为null,则抛出NPE checkNotNull(e); // 获取锁,并加锁 final ReentrantLock lock = this.lock; lock.lock(); try { // 判断队列是否已满 if (count == items.length) { // 队列已满,返回false return false; } else { // 队列未满,调用enqueue来入队,并返回true(入队成功) enqueue(e); return true; } } finally { // 释放锁 lock.unlock(); } }
/** * 向队列中添加元素(放入队尾),返回添加元素是否成功 * 如果队列已满,那么将会抛出IllegalStateException异常 * 如果添加的元素为null,则会抛出NullPointerException异常 */ public boolean add(E e) { return super.add(e);//调用AbstractQueue的add方法 } // 这是AbstractQueue的add方法,ArrayBlockingQueue重写了offer方法 public boolean add(E e) { // 调用的ArrayBlockingQueue的offer方法 if (offer(e)) { return true; } else { throw new IllegalStateException("Queue full"); } }
阻塞式添加元素
/** * 元素入队,如果队列已满,则会阻塞直到元素入队完成 */ public void put(E e) throws InterruptedException { // 判断元素是否为null,如果为null,则抛出NPE checkNotNull(e); // 获取锁,并加锁(可中断) final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // 自旋锁,如果队列已满,则线程进行阻塞 while (count == items.length) { // 阻塞,等待notFull的signal notFull.await(); } // 队列未满,进行入队操作 enqueue(e); } finally { // 释放锁 lock.unlock(); } }
除了上面一直阻塞,ArrayBlockingQueue还提供了超时时长的入栈接口
/** * 元素入队,并设置超时时长 */ public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { // 判断元素是否为null,如果为null,则抛出NPE checkNotNull(e); // 时间转换为纳秒 long nanos = unit.toNanos(timeout); // 加锁(可中断) final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // 如果队列已满,并且已经超时,则返回false(入队失败) while (count == items.length) { if (nanos <= 0) { return false; } // 阻塞,返回剩余的超时时长 nanos = notFull.awaitNanos(nanos); } // 队列未满,进行入队操作,并返回true(入队成功) enqueue(e); return true; } finally { // 释放锁 lock.unlock(); } }
入队操作
上面的offer、add、put操作中,真正执行入队操作的是enqueue方法,如下:
/** * 将元素插入putIndex所指的位置 */ private void enqueue(E x) { // 获取队列数组 final Object[] items = this.items; // 将元素放入数组元素中(队尾) items[putIndex] = x; // 如果队列已满,那么重置putIndex(因为是循环数组实现队列,所以下一个放入的位置为队首) if (++putIndex == items.length) { putIndex = 0; } // 元素数量加1 count++; // 唤醒notEmpty(通知队列不为空,有元素) notEmpty.signal(); }
注意,enqueue中的几个点,入队、队列size+1,唤醒notEmpty,这些操作都是在ReentrantLock加锁成功后执行的(offer方法中),所以不存在并发问题。
获取元素
这里的获取元素,是指调用peek接口,peek方法并不会将元素出队,而是只返回队首元素。
/** * 获取元素(不出队),如果队列为空,则返回null */ public E peek() { // 加锁 final ReentrantLock lock = this.lock; lock.lock(); try { // 获取队列指定位置的元素(takeIndex,也就是下一个出队的元素位置) return itemAt(takeIndex); } finally { lock.unlock(); } } /** * 获取队列数组中的i位置上的元素 */ @SuppressWarnings("unchecked") final E itemAt(int i) { return (E) items[i]; }
元素出队
与元素入队相似,出队也分阻塞式和非阻塞式;
非阻塞式出队
/** * 非阻塞式出队 * * @return 出队的元素(如果队列为空,则返回null) */ public E poll() { // 加锁 final ReentrantLock lock = this.lock; lock.lock(); try { // 如果队列为空,则返回null,否则进行出队操作 return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } }
阻塞式出队
/** * 出队,设置超时时间 */ 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) { // 如果已经超时,则返回null if (nanos <= 0) { return null; } // 线程阻塞,等待notEmpty的signal nanos = notEmpty.awaitNanos(nanos); } // 队列不为空,则进行出队 return dequeue(); } finally { lock.unlock(); } }
/** * 出队,阻塞式 */ public E take() throws InterruptedException { // 加锁 final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // 如果队列为空,则进行阻塞,等待notEmpty.signal while (count == 0) { notEmpty.await(); } // 进行出队 return dequeue(); } finally { // 释放锁 lock.unlock(); } }
出队操作
上面的poll、take中,真正执行出队操作的是dequeue方法,如下:
/** * 出队操作 */ private E dequeue() { // 获取队列数组 final Object[] items = this.items; // 获取队列数组中队首的元素 @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; // 将队首元素设置为null(释放) items[takeIndex] = null; // 如果takeIndex达到数组长度,那么就重设队首指针 if (++takeIndex == items.length) { takeIndex = 0; } // 队列元素减1 count--; // 如果itrs不为null,则调用元素出队接口(elementDequeue) if (itrs != null) { itrs.elementDequeued(); } // notFull唤醒(表示可以继续入队元素) notFull.signal(); // 返回出队的元素 return x; }
获取队列元素数量
/** * 获取队列元素数量 * 注意,获取size也加锁了,这是因为避免有其他线程正在出队或者入队,导致获取的size不正确 * 加了锁,能够保证获取size时,队列没有被修改 */ public int size() { final ReentrantLock lock = this.lock; lock.lock(); try { return count; } finally { lock.unlock(); } }
原文地址:https://www.cnblogs.com/-beyond/p/14407201.html