ArrayBlockingQueue相对来说就比较简单了。

ArrayBlockingQueue是基于数组的阻塞队列,创建的时候需要指定容量,也就是说ArrayBlockingQueue是有界队列。

ArrayBlockingQueue#主要属性

items:也就是ArrayBlockingQueue的底层数据结构

final Object[] items;

takeIndex:下次出队列的位置,包括take, poll, peek or remove等操作。

putIndex:下次入队列的位置,包括put, offer, or add等操作。

count:队列实际长度。

lock:ReentrantLock,出、入队列的时候都需要上锁。

notEmpty:lock的Condition,协助获取数据的线程排队。

notFull:lock的Condition,协助加入队列的线程排队。

构造方法

采用非公平锁创建容量为capacity的队列:

public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

创建容量为capacity的队列,fair参数指定使用公平锁或非公平锁:

  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();
    }

创建一个非空的队列,如果初始化集合容量超过了指定的队列容量则抛出异常,初始化数据的过程中统计队列实际容量count、以及putIndex:

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 : i;
        } finally {
            lock.unlock();
        }
    }

入队和出队

与LinkedBlockingQueue类似,ArrayBlockingQueue也提供了两类出队、入队方法。

阻塞方法:put,take。
非阻塞方法:add、offer、poll, peek ,remove。

阻塞方法put在队列满的时候通过notFull、take在队列空的时候通过notEmpty阻塞当前线程,直到条件满足再唤醒返回。

非阻塞的入队方法在队列未满是完成入队返回true、队列满的话立即返回false。非阻塞的出队方法在队列空的时候立即返回null。

加锁、阻塞、唤醒方式与LinkedBlockingQueue大同小异,所以代码就不贴出了。

不过ArrayBlockingQueue的入队和出队采用了一个比较巧妙的算法:putIndex、takeIndex、count初始化为0,每入队一个元素则putIndex++、count++,putIndex达到队列容量item.length后再从0开始。takeIndex的操作逻辑类似,每出队一个元素则takeIndex++、count--,takeIndex达到队列容量后再从0开始。

每次入队的时候都会检查队列容量是否允许当前元素入队,这样就能确保putIndex、takeIndex始终保持在一个正确的位置,他们两个就像是在一段长度有限的笔直公路上始终同向、反复循环行驶的小汽车,在count的协调下他们两个永远不会相遇。

区别

简单说一下SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue的显而易见的区别或者说各自的特点,不一定全面:

  1. 首先他们都是BlockingQueue,是线程安全的,多线程环境下可以放心大胆使用
  2. SynchronousQueue是无界队列,队列中保存的是生产者生产的数据、或者是消费者的获取数据的请求,只提供阻塞方法(底层都是通过transfer完成的)
  3. LinkedBlockingQueue的底层是列表结构,可以是有界的、也可以是无界的,队列中只存储生产者的数据。提供阻塞和非阻塞两种入队、出队操作,通过可重入锁的Condition来阻塞线程
  4. ArrayBlockingQueue的底层是数组,是有界队列,队列中只存储生产者的数据。提供阻塞和非阻塞两种入队、出队操作,通过可重入锁的Condition来阻塞线程

小结

三种BlockingQueue的源码分析完毕,接下来可以开始学习线程池ThreadPoolExecutor的相关内容了。

Thanks a lot!

上一篇 BlockingQueue - LinkedBlockingQueue
下一篇 线程池 - ThreadPoolExecutor源码分析


45 声望17 粉丝