阻塞队列之ArrayBlockingQueue源码分析

阻塞队列之ArrayBlockingQueue

上一篇文章笔主介绍了BlockingQueue的实现类之一,即LinkedBlockingQueue,了解了其内部的具体实现。今天,我们再来学习下BlockingQueue的另一个实现类ArrayBlockingQueue。

ArrayBlockingQueue类的定义

还是老套路,我们看下JDK中对ArrayBlockingQueue的定义,如下:

A bounded {@linkplain BlockingQueue blocking queue} backed by an array. This queue orders elements FIFO (first-in-first-out). The head of the queue is that element that has been on the queue the longest time. The tail of the queue is that element that has been on the queue the shortest time. New elements are inserted at the tail of the queue, and the queue retrieval operations obtain elements at the head of the queue.

This is a classic "bounded buffer", in which a fixed-sized array holds elements inserted by producers and extracted by consumers. Once created, the capacity cannot be changed. Attempts to {@code put} an element into a full queue will result in the operation blocking; attempts to {@code take} an element from an empty queue will similarly block.

This class supports an optional fairness policy for ordering waiting producer and consumer threads. By default, this ordering is not guaranteed. However, a queue constructed with fairness set to {@code true} grants threads access in FIFO order. Fairness generally decreases throughput but reduces variability and avoids starvation.

ArrayBlockingQueue是有数组实现的有界队列。这个队列要求元素“先进先出”,队列头部的元素在队列中存活时间最长,队列尾部的元素在队列中存活时间最短。新元素会被插入队列的尾部,而且移除元素时从队列的头部进行遍历操作。

这是一个经典的"有界缓冲区",由一个固定大小的数组保存生产者插入的元素并消费者提取。队列一旦创建完成,容量就不能被改变。尝试向满队列的插入操作会被阻塞,尝试从空队列的移除操作同样会被阻塞。

ArrayBlockingQueue提供了可选择的公平策略用来给生产者和消费者线程排序。默认情况下,不保证此排序。然而,构造公平的队列要设置为true来允许线程按照FIFO的顺序访问队列。公平通常会降低队列的吞吐量但是可以减少可变性且避免饥饿。

ArrayBlockingQueue类的特点

简单总结下,ArrayBlockingQueue有以下特点:

  • 内部基于循环数组实现,创建时必须制定数组的容量。
  • 执行插入或者移除元素时必须获取lock。
  • 提供可选择的公平策略来保证按照FIFO的顺序访问队列。

接下来,我们从源码的角度来认识下ArrayBlockingQueue。

ArrayBlockingQueue类的成员属性

ArrayBlockingQueue主要定义了以下成员属性,源码如下:

    //队列基于数组实现,用来放置元素的数组
    final Object[] items;

    //下次移除数组元素的下标
    int takeIndex;

    //下次插入数组元素的下标
    int putIndex;

    //队列中的元素个数
    int count;

    //所有对队列的操作都需要使用此锁
    final ReentrantLock lock;

    //非空条件对象,队列为空时移除元素的线程会被阻塞
    private final Condition notEmpty;

     //非满条件对象,队列为满时插入元素的线程会被阻塞
    private final Condition notFull;
    
    //当前活动迭代器的共享状态,如果已知没有,则为空。允许队列操作更新迭代器状态。
    transient Itrs itrs = null;
ArrayBlockingQueue类的构造方法

ArrayBlockingQueue提供了三种方式来构造阻塞队列,分别是:

  1. 指定队列容量,但使用默认的访问策略(非公平)的构造方法。
  2. 指定队列容量,使用参数指定的访问策略的构造方法。
  3. 指定队列容量,使用参数指定的访问策略,同时将参数给定集合中的元素依次插入队列中的构造方法。

构造方法的源码如下:

    //指定队列容量,但使用默认的访问策略(非公平)
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    //指定队列容量,使用参数指定的访问策略
    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();
    }

    //指定队列容量,使用参数指定的访问策略,同时将参数给定集合中的元素依次插入队列
    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的值
        } finally {
            lock.unlock();  //释放锁
        }
    }
ArrayBlockingQueue类的成员方法

ArrayBlockingQueue类的成员方法可以划分为以下几类:

  1. 公共方法
  2. 插入方法
  3. 移除方法

这里公共方法是指在类内部会被多次调用的方法或者是获取队列属性的方法,例如:根据给定数组下标获取队列元素的方法、检查数据非空的方法、真正向队列中插入元素(enqueue)的方法、真正从队列中移除元素(dequeue)的方法、根据给定数组下标删除队列元素的方法、获取队列元素个数的方法、清空整个队列的方法、将队列转换为数组的方法等等。源码如下:

     //对给定的i递减
    final int dec(int i) {
        return ((i == 0) ? items.length : i) - 1;
    }

    //获取给定数组下标的队列元素
    @SuppressWarnings("unchecked")
    final E itemAt(int i) {
        return (E) items[i];
    }

    //检查数据非空
    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;  //将元素x放入数组的putIndex位置
        if (++putIndex == items.length)  //如果插入元素后数组已满
            putIndex = 0;  //putIndex重新置为0
        count++;  //队列中的元素个数+1
        notEmpty.signal(); //唤醒阻塞的消费者线程
    }

    //从队列中移除元素
    private E dequeue() {
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];  //获取数组takeIndex位置的元素
        items[takeIndex] = null;  //将数组takeIndex位置的元素置为null
        if (++takeIndex == items.length)  //如果移除是数组的最后一个元素
            takeIndex = 0;  //takeIndex重新置为0
        count--;  //队列中的元素个数-1
        if (itrs != null)  //如果迭代器不为null
            itrs.elementDequeued();  //更新迭代器状态
        notFull.signal();  //唤醒阻塞的生产者线程
        return x;
    }

    //移除给定数组下标的元素
    void removeAt(final int removeIndex) {
        final Object[] items = this.items;
        if (removeIndex == takeIndex) {
            items[takeIndex] = null;  //将数组takeIndex位置的元素置为null
            if (++takeIndex == items.length)  //如果移除是数组的最后一个元素
                takeIndex = 0;  //takeIndex重新置为0
            count--;  //队列中的元素个数-1
            if (itrs != null)  //如果迭代器不为null
                itrs.elementDequeued();  //更新迭代器状态
        } else {
            final int putIndex = this.putIndex;
            for (int i = removeIndex;;) {  //此处对于数组的操作体现出是一个循环数组
                int next = i + 1; 
                if (next == items.length) //如果移除的是数组的最后一个元素
                    next = 0;  //next置为0,便于从数组下标0开始继续寻找
                if (next != putIndex) {  
                    items[i] = items[next]; //将removeIndex后的数组元素依次前移
                    i = next;  //  i = i + 1
                } else {
                    items[i] = null;  //将数组removeIndex位置的元素置为null
                    this.putIndex = i;  //将下次插入数组元素的下标更新为i
                    break;  //跳出循环
                }
            }
            count--;  //队列中的元素个数-1
            if (itrs != null)  //如果迭代器不为null
                itrs.removedAt(removeIndex);  //更新迭代器状态
        }
        notFull.signal();  //唤醒阻塞的生产者线程
    }
   
   //查询takeIndex位置上的元素
  public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();  //获取锁
        try {
            //队列为空时返回nul
            return itemAt(takeIndex); // null when queue is empty
        } 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();  //释放锁
        }
    }
   
   //寻找队列中是否存在等于o的节点
   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;  //如果找到返回true
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();  //释放锁
        }
    }

    //将队列转换为Object数组
    public Object[] toArray() {
        Object[] a;
        final ReentrantLock lock = this.lock;
        lock.lock();  //获取锁
        try {
            final int count = this.count;
            a = new Object[count]; //新数组
            int n = items.length - takeIndex;
            if (count <= n)
                System.arraycopy(items, takeIndex, a, 0, count);
            else {
                System.arraycopy(items, takeIndex, a, 0, n);
                System.arraycopy(items, 0, a, n, count - n);
            }
        } finally {
            lock.unlock();  //释放锁
        }
        return a;
    }

    //将队列转换为T类型数组
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();  //获取锁
        try {
            final int count = this.count;
            final int len = a.length;
            if (len < count)
                a = (T[])java.lang.reflect.Array.newInstance(
                    a.getClass().getComponentType(), count);
            int n = items.length - takeIndex;
            if (count <= n)
                System.arraycopy(items, takeIndex, a, 0, count);
            else {
                System.arraycopy(items, takeIndex, a, 0, n);
                System.arraycopy(items, 0, a, n, count - n);
            }
            if (len > count)
                a[count] = null;
        } finally {
            lock.unlock();  //释放锁
        }
        return a;
    }
 
   //使用字符串表示队列中的元素
    public String toString() {
        final ReentrantLock lock = this.lock;
        lock.lock(); //获取锁
        try {
            int k = count;
            if (k == 0)
                return "[]";

            final Object[] items = this.items;
            StringBuilder sb = new StringBuilder();
            sb.append('[');
            for (int i = takeIndex; ; ) {
                Object e = items[i];
                sb.append(e == this ? "(this Collection)" : e);
                if (--k == 0)
                    return sb.append(']').toString(); //将元素追加到StringBuilder对象中
                sb.append(',').append(' ');
                if (++i == items.length)
                    i = 0;
            }
        } 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;  //队列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();  //释放锁
        }
    }

    //返回队列的迭代器对象
    public Iterator<E> iterator() {
        return new Itr();
    }

ArrayBlockingQueue插入操作提供了add(E e)、put(E e)、offer(E e, long timeout, TimeUnit unit)、offer(E e)这四个方法,这四个方法的特点如下:

  • add(E e):如果队列已满,会抛出IllegalStateException(“Queue full”)。否则,在队列的putIndex位置执行插入操作。
  • put(E e):如果队列已满,插入操作会阻塞直至队列有空闲位置。
  • offer(E e, long timeout, TimeUnitunit):如果队列已满,插入操作在给定的时间内会阻塞。如果阻塞结束队列还是已满,返回false。否则,在队列的putIndex位置执行插入操作。
  • offer(E e):如果队列已满,返回false。否则,在队列的putIndex位置执行插入操作。

这四个方法的源码如下:

    /**
     * 如果队列已满,会抛出IllegalStateException("Queue full")。
     * 否则,在队列的putIndex位置执行插入操作。
     */
   public boolean add(E e) {
        /**
         * 注意这里调用了父类AbstractQueue的add(E e),而a父类的dd(E e)中又调用了自己实现的offer(e)。
         * 如果offer(e)返回true,此方法也返回true。否则,此方法抛出IllegalStateException("Queue full")。
         */
        return super.add(e);
    }

     //如果队列已满,返回false。否则,在队列的putIndex位置执行插入操作。
    public boolean offer(E e) {
        checkNotNull(e);  //检查元素是否为null
        final ReentrantLock lock = this.lock;
        lock.lock();  //获取锁
        try {
            if (count == items.length)
                return false;  //队列已满直接返回false
            else {
                enqueue(e);  //执行插入操作
                return true;  //返回true
            }
        } finally {
            lock.unlock();  //释放锁
        }
    }

    //如果队列已满,插入操作会阻塞直至队列有空闲位置。
    public void put(E e) throws InterruptedException {
        checkNotNull(e);  //检查元素是否为null
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();  //获取锁
        try {
            while (count == items.length)  //如果队列已满,阻塞当前插入操作
                notFull.await();  //阻塞
            enqueue(e);  //执行插入操作
        } finally {
            lock.unlock();  //释放锁
        }
    }

    //如果队列已满,插入操作在给定的时间内会阻塞。
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        checkNotNull(e);  //检查元素是否为null
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();  //获取锁
        try {
            while (count == items.length) {
                if (nanos <= 0)  //阻塞结束,队列还是已满状态,返回false
                    return false;
                //插入操作给定的时间内会阻塞
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(e);  //执行插入操作
            return true;  //返回true
        } finally {
            lock.unlock();   //释放锁
        }
    }

ArrayBlockingQueue移除操作提供了 take()、poll(long timeout, TimeUnit unit)、poll()、remove(Object o) 这四个方法,这四个方法的特点如下:

  • take():如果队列为空,移除操作会阻塞直至队列中有元素。
  • poll(long timeout, TimeUnit unit):如果队列为空,移除操作在给定的时间会阻塞。如果阻塞结束队列还是为空,返回false。否则,从队列的takeIndex移除元素。
  • poll():如果队列为空,直接返回null。否则,从队列的takeIndex移除元素。
  • remove(Object o) :不同于以上三个方法,remove(Object o)并非是从队列的头部移除元素,而是根据给定的元素o在队列中查找。如果找到元素o,就将元素o从队列中删除。

这四个方法的源码如下:

   /**
    * 如果队列为空,直接返回null。
    * 否则,从队列的takeIndex移除元素。
    */
   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();   //释放锁
        }
    }

    //如果队列为空,移除操作在给定的时间会阻塞。
    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)  //阻塞结束队列还是为空,直接返回null
                    return null;
                //移除操作在给定的时间内会阻塞
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();  //执行移除操作
        } finally {
            lock.unlock();   //释放锁
        }
    }

   //从队列中删除元素
   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;  //返回true
                    }
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;  //未找到给定的元素,返回false
        } finally {
            lock.unlock();   //释放锁
        }
    }
总结

至此,我们通过分析源码分方式学习了ArrayBlockingQueue。再次总结下其特点:

  • 内部基于循环数组实现
  • 初始化时必须指定队列的大小
  • 执行插入或者移除元素时必须获取lock
  • 提供可选择的公平策略来保证按照FIFO的顺序访问队列

那么,ArrayBlockingQueue与LinkedBlockingQueue相比,有什么不同之处?

  • LinkedBlockingQueue内部基于链表实现,ArrayBlockingQueue基于数组实现。
  • LinkedBlockingQueue如果初始化不指定队列的容量,默认容量就是Integer.MAX_VALUE。但是,ArrayBlockingQueue初始化时必须指定队列的容量。
  • LinkedBlockingQueue始终按照FIFO的策略访问队列中的元素,而ArrayBlockingQueue只有在选择了公平策略的情况下才会按照FIFO的策略访问队列中的元素。
  • LinkedBlockingQueue使用两个锁分别控制插入和移除操作,ArrayBlockingQueue使用一个锁控制插入和移除操作。因此,LinkedBlockingQueue的插入和移除操作可以并行,队列的吞吐量更好。

由于笔主水平有限,笔误或者不当之处还请批评指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值