Android之ArrayBlockingQueue源码解析

上一篇文章Android之阻塞队列LinkedBlockingQueue使用及源码解析讲到了LinkedBlockingQueue,与之相关的一个队列就是今天要讲到的ArrayBlockingQueue。
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;

    /**
     * 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;

可以看出来定义了一个数组去维护数据,而不是像LinkedBlockingQueue用链表维护数据;然后定义了一个锁和出队入队的判断条件,同样跟链表不同,链表定义了两把锁分别用于管理出队和入队,从这里也可以看出来,在高并发的情况下,链表队列可能会比数组队列更好用,效率更高。

再看构造方法

/**
     * Creates an {@code ArrayBlockingQueue} with the given (fixed)
     * capacity and default access policy.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity < 1}
     *
     * 指定数组大小
     */
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    /**
     * 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}
     *
     * 指定数组大小,
     * fair 为true 先来的线程先操作 为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();
    }

    /**
     * Creates an {@code ArrayBlockingQueue} with the given (fixed)
     * capacity, the specified access policy and initially containing the
     * elements of the given collection,
     * added in traversal order of the collection's iterator.
     *
     * @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.
     * @param c the collection of elements to initially contain
     * @throws IllegalArgumentException if {@code capacity} is less than
     *         {@code c.size()}, or less than 1.
     * @throws NullPointerException if the specified collection or any
     *         of its elements are 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) {
                    if (e == null) throw new NullPointerException();
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            //数组元素个数
            count = i;
            //下一次入队的索引
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

构造方法里会对数组初始化,并且通过fair参数来构造一个公平锁或者不公平锁。

接下来看看入队方法

public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //数组如果满了,那就阻塞在这里
            while (count == items.length)
                notFull.await();//一直等待条件notFull,即被其他线程唤醒,就是有线程将一个元素出队了,然后调用notFull.signal()唤醒其他等待这个条件的线程

            //将元素入队
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        //如果调用第一个或者第二个构造方法,那putIndex就是0
        items[putIndex] = x;
        //如果下一次入队索引等于数组长度,就把索引置为0,下次就从数组头部开始插入元素,循环数组
        if (++putIndex == items.length) putIndex = 0;
        count++;
        //唤醒因为数组为空而阻塞的线程
        notEmpty.signal();
    }

入队逻辑其实挺简单的,也没啥好说的

再来看看出队方法

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lockInterruptibly();
        try {
            //如果数组里没有元素,就阻塞
            while (count == 0)
                notEmpty.await();

            //取出元素并返回
            return dequeue();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        //如果一次都没出队,那takeIndex = 0
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;//取出该索引下的元素然后将数组该索引下的置为null,方便回收
        //如果取完数据后数组为null,那下次从0索引出取数据,就这样循环
        if (++takeIndex == items.length) takeIndex = 0;
        //数组数量减一
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        //唤醒因为数组已满而等待入队的线程
        notFull.signal();
        return x;
    }

总结以上出队入队的逻辑就是:
调用put方法入队,如果数组已满,那就调用notFull.await(),这个A线程就会放弃锁进入阻塞状态;如果这时候有B线程调用take方法取走了一个数据,调用notFull.signal()唤醒等待的线程,这时候A线程就被唤醒获取锁,调用enqueue()方法把数据插入到数组。
调用take方法出队,如果数组为空,那就调用notEmpty.await(),这个C线程就会放弃锁进入阻塞状态,如果这时候有D线程调用put方法添加了一个数据,调用了notEmpty.signal();唤醒等待线程,这时候C线程就被唤醒获取锁,调用dequeue()方法把数据从数组取出。

其它出队入队方法与LinkedBlockingQueue类的方法类似。

现在用图说明出队入队逻辑:

1.假设数组大小是3,然后入队了三个元素
这里写图片描述

2.接下来开始出队,出队索引默认是0,这个位置值变为null
这里写图片描述

3.接下来继续出队,这时候索引是1,就将数组item1的值变为null
这里写图片描述

4.接下来开始入队,由于第一步入队操作,添加了三个元素,跟数组大小一样,入队索引变为0
这里写图片描述

这里我们就可以比较下ArrayBlockingQueue(简称abq)和LinkedBlockingQueue(简称lbq):
1.abq使用一把锁来对出队入队操作进行控制,表面同一时刻只能进行出队操作或者入队操作;lbq使用两把锁分别控制出队和入队,说明同一时刻可以同时进行出队和入队操作;显然lbq中在高并发的时候阻塞情况会比abq少,性能会优于abq。
2.两者在出队入队操作时都是用锁,所以都是线程安全的实现。
3.abq使用的是一个定长数组,且在出队入队的时候是循环操作数组,看上面代码注释可知,相比于lbq来说内存利用率方面会占优势;不过abq在初始化的时候就会提前给数组分配内存,而lbq是使用多少就分配多少内存,从这点来看lbq在内存方面又小占优势。

这个类的使用跟上一篇文章的LinkedBlockingQueue使用几乎一样,只不过abq不同同时进行添加元素和删除元素操作,而lbq可以。

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/80785171