Java ArrayBlockingQueue source code in-depth analysis

Get into the habit of writing together! This is the 10th day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

Based on JDK1.8, the underlying source code implementation of ArrayBlockingQueue is introduced in detail, including the principle of logical ring array, and the source code of operations such as queue entry and queue out. In fact, the source code of ArrayBlockingQueue is very simple!

1 Overview of ArrayBlockingQueue

public class ArrayBlockingQueue< E > extends AbstractQueue< E > implements BlockingQueue< E >, Serializable

ArrayBlockingQueue comes from the JUC package of JDK1.5 and is a bounded blocking queue that supports concurrent operations. This queue sorts elements on a first-in-first-out (FIFO) basis. The bottom layer saves data in the form of an array (logically it can be regarded as a circular array, that is, the position of the head and tail of the queue is not fixed, but the head and tail are logically consecutive elements).

The capacity must be passed in when ArrayBlockingQueue is created, and once created, the capacity cannot be changed. Continuing to stuff elements into a full queue will cause the current thread to block. If you get an element from an empty queue, it will also cause the current thread to block.

Supports an optional fairness strategy for sorting waiting producer threads and consumer threads, that is, all blocked producer threads or consumer threads access the queue in the order of blocking, that is, the first blocked producer thread is available in the queue You can insert elements into the queue first, and the consumer thread that blocks first can get elements from the queue first when the queue is available. In order to guarantee fairness, throughput is usually reduced, so the default is an unfair implementation!

Implements the Serializable interface, supports serialization, does not implement Cloneable, does not support cloning!

Null elements are not supported!

insert image description here

2 The principle of ArrayBlockingQueue

2.1 Main attributes

ArrayBlockingQueue内部是采用一个items数组进行数据存储的。使用ReentrantLock作为锁,使用notEmpty、notFull两个Condition来分别实现消费者、生产者的等待唤醒,当获消费者线程被阻塞时会将该线程放置到notEmpty条件队列中,当插入数据的生产者线程被阻塞时,会将该线程放置到notFull条件队列中。

使用count来保存元素的数量,使用takeIndex来保存将取元素的下标索引,代表逻辑头部,使用putIndex将存元素的下标索引,代表队列逻辑尾部,这里的“逻辑”表示队列元素在数组中“逻辑上是连续的”,就队列头尾没有空余的空间,但是物理上可能不连续,比如起始索引在数组尾部,结束索引在数组头部,这里的数组相当于一个逻辑环形数组,有利于空间的利用!

/**
 * 底层存放元素的数组
 */
final Object[] items;

/**
 * 将取元素的下标索引,代表逻辑头部
 */
int takeIndex;

/**
 * 将存元素的下标索引,代表队列逻辑尾部
 */
int putIndex;

/**
 * 队列中元素的数量
 */
int count;

/**
 * 可重入锁lock
 */
final ReentrantLock lock;

/**
 * 存放等待消费的消费者的条件队列
 */
private final Condition notEmpty;

/**
 * 存放等待生产的生产者的条件队列
 */
private final Condition notFull;
复制代码

2.2 构造器

2.2.1 ArrayBlockingQueue( capacity )

public ArrayBlockingQueue(int capacity)

创建一个指定容量和默认非公平访问策略的 ArrayBlockingQueue。如果 capacity 小于 1,将会抛出IllegalArgumentException。

/**
 * 创建一个带有指定容量和默认非公平访问策略的 ArrayBlockingQueue。
 *
 * @param capacity 指定容量
 * @throws IllegalArgumentException 如果 capacity 小于 1
 */
public ArrayBlockingQueue(int capacity) {
    //内部调用另一个构造器,公平策略为false—非公平模式
    this(capacity, false);
}
复制代码

2.2.2 ArrayBlockingQueue(capacity, fair)

public ArrayBlockingQueue(int capacity, boolean fair)

创建一个具有指定容量和指定访问策略的 ArrayBlockingQueue。如果 capacity 小于 1,将会抛出IllegalArgumentException。

/**
 * 创建一个具有指定容量和指定访问策略的 ArrayBlockingQueue。
 *
 * @param capacity 指定容量
 * @param fair     如果为 true,则按照 FIFO 顺序访问插入或移除时受阻塞线程的队列,即公平模式;如果为 false,则访问顺序是不确定的,即非公平模式。
 * @throws IllegalArgumentException 如果 capacity 小于 1
 */
public ArrayBlockingQueue(int capacity, boolean fair) {
    //capacity校验
    if (capacity <= 0)
        throw new IllegalArgumentException();
    //初始化数组
    this.items = new Object[capacity];
    //初始化lock锁
    lock = new ReentrantLock(fair);
    //初始化两个条件队列
    notEmpty = lock.newCondition();
    notFull = lock.newCondition();
}
复制代码

2.2.3 ArrayBlockingQueue(capacity, fair, c)

public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)

创建一个具有指定容量和指定访问策略的 ArrayBlockingQueue,它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。

如果 capacity 小于 c.size(),或者小于 1,将会抛出IllegalArgumentException;如果指定 collection 或任何内部元素为 null,将会抛出NullPointerException。

/**
 * 创建一个具有指定容量和指定访问策略的 ArrayBlockingQueue
 * 它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。
 *
 * @param capacity 指定容量
 * @param fair     如果为 true,则按照 FIFO 顺序访问插入或移除时受阻塞线程的队列;如果为 false,则访问顺序是不确定的。
 * @param c        指定集合
 * @throws IllegalArgumentException 如果 capacity 小于 c.size(),或者小于 1
 * @throws NullPointerException     如果指定 collection 或任何内部元素为 null
 */
public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
    //调用两个参数的构造器初始化全局属性
    this(capacity, fair);

    final ReentrantLock lock = this.lock;
    //加锁,这个加锁的操作并不是为了互斥操作,而是保证其可见性。比如count、putIndex
    lock.lock(); // Lock only for visibility, not mutual exclusion
    try {
        int i = 0;
        try {
            //遍历指定集合
            for (E e : c) {
                //元素null检测
                checkNotNull(e);
                //存放数据
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            //如果容量超过capacity
            throw new IllegalArgumentException();
        }
        //计数器
        count = i;
        //下一次要存放元素的索引,如果等于capacity,那么置为0,回到数组头部
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        //解锁
        lock.unlock();
    }
}
复制代码

2.3 入队操作

2.3.1 put( e )方法

public void put(E e)

将指定的元素插入此队列的尾部,如果该队列已满,则线程等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素为 null则抛出NullPointerException 异常。

大概步骤为:

  1. 指定元素e的null检测;
  2. 可中断的等待获取锁,即响应中断,获取不到就在该锁的同步队列中等待被唤醒,同时响应中断;
  3. 获取到锁之后,循环判断count是否等于容量,如果是,说明队列满了,那么调用notFull.await在notFull条件队列中等待,直到被唤醒,被唤醒之后继续循环;
  4. 如果队列不满,则调用enqueue存放元素;
  5. 无论过程中发生了什么,最后的finally中解锁;
/**
 * 将指定的元素插入此队列的尾部,如果该队列已满,则线程等待。
 *
 * @throws InterruptedException 在等待时被中断
 * @throws NullPointerException 如果指定元素为 null
 */
public void put(E e) throws InterruptedException {
    // e的检测,为空则抛出NullPointerException异常
    checkNotNull(e);
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        //如果count等于容量,说明队列满了
        while (count == items.length)
            //那么该线程在notFull条件队列中等待
            notFull.await();
        //队列不满,调用enqueue存放元素
        enqueue(e);
    } finally {
        //解锁
        lock.unlock();
    }
}

/**
 * 元素null值检测
 *
 * @param v 被检测元素
 */
private static void checkNotNull(Object v) {
    if (v == null)
        throw new NullPointerException();
}
复制代码

2.3.1.1 enqueue存放元素

enqueue用于插入元素到下一个索引位置,当队列没有满的时候调用的此方法。

该方法中putIndex作为即将插入元素的索引,插入之后putIndex自增一,作为下一个要插入的元素的索引,如果putIndex自增之后等于容量,说明到了数组末尾,因此putIndex=0,即下一个要存放的索引从0开始。

存入元素之后,count会自增,同时尝试唤醒在notEmpty条件队列中等待的消费线程。

/**
 * 插入元素到下一个索引位置
 *
 * @param x 指定元素
 */
private void enqueue(E x) {
    //获取当前数组
    final Object[] items = this.items;
    // 将当前值插入到数组的putIndex索引位置
    items[putIndex] = x;
    //如果putIndex+1等于数组的长度,说明此时存放元素的下标走到了数组的末尾
    if (++putIndex == items.length)
        //则初始化putIndex=0,回到数组的开头
        putIndex = 0;
    //上面的if可以看出这个数组是个逻辑环形数组,这样每次插入、移除的时候不需要复制移动数组中的元素,同时能更加有效的利用空间
    //计数器自增1
    count++;
    //唤醒在notEmpty等待的消费线程
    notEmpty.signal();
}
复制代码

2.3.2 offer( e )方法

public boolean offer(E e)

将指定的元素插入到此队列的尾部。在成功时返回 true,如果此队列已满,则不阻塞,则立即返回 false。

相比于put方法,如果因为获取不到锁而在同步队列中等待的时候被中断也会继续等待获取锁,即不响应中断。如果e元素为null则抛出NullPointerException异常。

另外这里的“不会阻塞”是说的获取锁之后如果发现此队列已满,则立即返回 false,而不会阻塞在条件队列上!因此如果该锁被其他线程获取了,当前调用offer方法的线程还是会因为获取不到锁而被阻塞在lock的同步队列中!

/**
 * 将指定的元素插入到此队列的尾部.在成功时返回 true,如果此队列已满,则立即返回 false。
 *
 * @throws NullPointerException 如果e元素为null
 */
public boolean offer(E e) {
    // e的检测,为空则抛出NullPointerException异常
    checkNotNull(e);
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        /*如果count等于容量,说明队列满了*/
        if (count == items.length)
            //直接返回false
            return false;
        /*否则,调用enqueue存放元素*/
        else {
            enqueue(e);
            //返回true
            return true;
        }
    } finally {
        //解锁
        lock.unlock();
    }
}
复制代码

2.3.3 offer(e, timeout, unit)

public boolean offer(E e, long timeout, TimeUnit unit)

将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素e为 null则抛出NullPointerException 异常。

/**
 * 将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
 *
 * @param e       指定元素
 * @param timeout 时间
 * @param unit    时间单位
 * @return 如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。
 * @throws InterruptedException 因为获取不到锁而等待时被中断
 * @throws NullPointerException 如果e元素为null
 */
public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
    // e的检测,为空则抛出NullPointerException异常
    checkNotNull(e);
    //计算超时时间纳秒
    long nanos = unit.toNanos(timeout);
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        /*如果count等于容量,说明队列满了*/
        while (count == items.length) {
            //如果剩余超时时间小于等于0,说明时间到了
            if (nanos <= 0)
                //直接返回false
                return false;
            //否则,该线程在notFull条件队列中等待nanos时间
            nanos = notFull.awaitNanos(nanos);
        }
        //队列不满,调用enqueue存放元素
        enqueue(e);
        //返回true
        return true;
    } finally {
        //解锁
        lock.unlock();
    }
}
复制代码

2.3.4 add( e )方法

public boolean add(E e)

将指定元素插入此队列中。成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException,如果e元素为null则抛出NullPointerException 异常。当使用有容量限制的队列时,通常首选 offer。

如果因为获取不到锁而在同步队列中等待的时候被中断也会继续等待获取锁,即不响应中断。如果e元素为null则抛出NullPointerException 异常。

内部实际上就是调用的offer,根据offer方法的返回值判断是否需要抛出异常!

/**
 * 将指定元素插入此队列中。成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
 * 当使用有容量限制的队列时,通常首选 offer。
 *
 * @param e 指定元素
 * @return 成功时返回 true
 * @throws IllegalStateException 如果当前没有可用的空间
 * @throws NullPointerException  如果e元素为null
 */
public boolean add(E e) {
    //调用父类 AbstractQueue的add方法
    return super.add(e);
}

/**
 * 将指定的元素插入到此队列中(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用空间,则抛出 IllegalStateException。
 * 如果 offer 成功,则此实现返回 true,否则抛出 IllegalStateException。
 *
 * @param e 指定元素
 * @return 如果此 collection 由于调用而发生更改,则返回 true
 * @throws IllegalStateException    如果此时由于容量限制不能添加元素
 * @throws ClassCastException       如果指定元素的类不允许将该元素添加到此队列中
 * @throws NullPointerException     如果指定元素为 null 并且此队列不允许 null 元素
 * @throws IllegalArgumentException 如果此元素的某些属性不允许将该元素添加到此队列中
 */
public boolean add(E e) {
    //实际上调用的offer操作
    if (offer(e))
        //如果offer成功则返回true
        return true;
    else
        //offer失败则抛出IllegalStateException
        throw new IllegalStateException("Queue full");
}
复制代码

2.4 出队操作

2.4.1 take()方法

public E take()

获取并移除此队列的头部,在元素变得可用(队列非空)之前一直等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

大概步骤为:

  1. 指定元素e的null检测;
  2. 可中断的等待获取锁,即响应中断,获取不到就在该锁的同步队列中等待被唤醒,同时响应中断;
  3. 获取到锁之后,循环判断count是否等于0,如果是,说明队列空了,那么调用notEmpty.await在notEmpty条件队列中等待,直到被唤醒,之后继续循环;
  4. 如果队列不空,则调用dequeue取出元素;
  5. 无论过程中发生了什么,最后的finally中解锁;
/**
 * 获取并移除此队列的头部
 *
 * @return 被移除的队列头部元素
 * @throws InterruptedException 因为获取不到锁而等待的时候被中断
 */
public E take() throws InterruptedException {
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        //如果count等于0,说明队列空了
        while (count == 0)
            //那么该线程在notEmpty条件队列中等待,被唤醒之后会继续循环
            notEmpty.await();
        //队列不空,调用dequeue取出元素
        return dequeue();
    } finally {
        //解锁
        lock.unlock();
    }
}
复制代码

2.4.1.1 dequeue获取并移除队列头

dequeue用于获取并移除下一个索引位置的元素(队头),当队列没有空的时候调用的此方法。

该方法中takeIndex作为即将取出元素的索引,取出之后putIndex自增一,作为下一个要插入的元素的索引,如果takeIndex自增之后等于容量,说明到了数组末尾,因此putIndex=0,即下一个要存放的索引从0开始。

存入元素之后,count会自减,同时尝试唤醒在notFull条件队列中等待的生产线程。

/**
 * 获取并移除此队列的头部,仅在获取锁之后调用
 *
 * @return 被移除的队列头部元素
 */
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    //获取takeIndex索引处的元素
            E x = (E) items[takeIndex];
    //该位置的元素置空
    items[takeIndex] = null;
    //如果takeIndex+1等于数组的长度,说明此时取出元素的下标走到了数组的末尾
    if (++takeIndex == items.length)
        //则初始化takeIndex=0,回到数组的开头
        takeIndex = 0;
    //上面的if可以看出这个数组是个逻辑环形数组,这样每次插入、移除的时候不需要复制移动数组中的元素,同时能更加有效的利用空间
    //计数器自减1
    count--;
    //如果itrs不为null,说明此前获取过迭代器
    if (itrs != null)
        //更新迭代器中的元素数据
        itrs.elementDequeued();
    //唤醒在notFull等待的消费线程
    notFull.signal();
    //返回x
    return x;
}
复制代码

2.4.2 poll()方法

public E poll()

获取并移除此队列的头,如果此队列为空,则返回 null。

相比于take方法,如果因为获取不到锁而在同步队列中等待的时候被中断也会继续等待获取锁,即不响应中断。

/**
 * @return 获取并移除此队列的头,如果此队列为空,则返回 null。
 */
public E poll() {
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        /*
         * 如果count等于0,说明队列空了,那么直接返回null,
         * 否则调用dequeue用于获取并移除下一个索引位置的元素
         */
        return (count == 0) ? null : dequeue();
    } finally {
        //解锁
        lock.unlock();
    }
}
复制代码

2.4.3 poll(timeout, unit)方法

public E poll(long timeout, TimeUnit unit)

获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。返回此队列的头部;如果在元素可用前超过了指定的等待时间,则返回 null。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

/**
 * 获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
 *
 * @param timeout 时间
 * @param unit    时间单位
 * @return 此队列的头部;如果在元素可用前超过了指定的等待时间,则返回 null
 * @throws InterruptedException 因为获取不到锁而等待时被中断
 */
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    //计算超时时间纳秒
    long nanos = unit.toNanos(timeout);
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        //如果count等于0,说明队列空了
        while (count == 0) {
            //如果剩余超时时间小于等于0,说明时间到了
            if (nanos <= 0)
                //直接返回null
                return null;
            //否则,该线程在notEmpty条件队列中等待nanos时间
            nanos = notEmpty.awaitNanos(nanos);
        }
        //队列不满,调用dequeue取出元素,返回返回值
        return dequeue();
    } finally {
        //解锁
        lock.unlock();
    }
}
复制代码

2.4.4 remove()方法

public E remove()

获取并移除此队列的头。此方法与 poll 唯一的不同在于此队列为空时将抛出一个NoSuchElementException异常。

相比于take方法,如果因为获取不到锁而在同步队列中等待的时候被中断也会继续等待获取锁,即不响应中断。

内部实际上就是调用的poll方法,根据poll方法的返回值判断是否需要抛出异常!

/**
 * 获取并移除此队列的头。此方法与 poll 唯一的不同在于此队列为空时将抛出一个NoSuchElementException异常。
 *
 * @return 队列头
 * @throws NoSuchElementException 此队列为空
 */
public E remove() {
    //直接调用poll方法,获取返回值x
    E x = poll();
    //如果x不为null,那么返回x;否则抛出NoSuchElementException异常
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
复制代码

2.4.5 remove( o )方法

public boolean remove(Object o)

从此队列中移除指定元素的单个实例(如果存在)。如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false。

这个方法就是从队列头开始遍历队列,查找和指定元素o使用equals比较返回true的元素,索引为i,然后调用removeAt(i) 移除i位置的元素,并将后面的元素前移一位,因此这个方法的性能比较差,一般不推荐使用!

/**
 * 从此队列中移除指定元素的单个实例(如果存在)。
 *
 * @param o 指定元素
 * @return 如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false
 */
public boolean remove(Object o) {
    //如果o为null,直接返回null
    if (o == null) return false;
    //获取数组
    final Object[] items = this.items;
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        //如果count大于0
        if (count > 0) {
            //获取下一个插入元素的索引(尾部之后)
            final int putIndex = this.putIndex;
            //获取下一个移除元素的索引(初始化为头部)
            int i = takeIndex;
            /*do while循环遍历队列*/
            do {
                //如果equals和头部元素比较相等
                if (o.equals(items[i])) {
                    //那么移除i位置的元素
                    removeAt(i);
                    //返回true
                    return true;
                }
                //i自增1,之后如果等于数组容量
                if (++i == items.length)
                    //i回到1
                    i = 0;
                //如果i 不等于 下一个插入元素的索引(尾部之后),那么就可以一直循环
                //如果相等,说明队列便利完毕也没找到相等的元素,结束循环
            } while (i != putIndex);
        }
        //返回false
        return false;
    } finally {
        //解锁
        lock.unlock();
    }
}

/**
 * 删除指定索引处的元素
 */
void removeAt(final int removeIndex) {
    // assert lock.getHoldCount() == 1;
    // assert items[removeIndex] != null;
    // assert removeIndex >= 0 && removeIndex < items.length;
    final Object[] items = this.items;
    /*如果要删除的元素索引就是头索引,那么类似于出队列的方法比如take*/
    if (removeIndex == takeIndex) {
        // removing front item; just advance
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    }
    /*否则,移动removeIndex后面元素,向前移动一位*/
    else {
        // an "interior" remove

        // slide over all others up through putIndex.
        final int putIndex = this.putIndex;
        //i从removeIndex开始
        for (int i = removeIndex; ; ) {
            //next等于i+1
            int next = i + 1;
            //如果等于容量
            if (next == items.length)
                //那么next置为0
                next = 0;
            //如果next不等于putIndex
            if (next != putIndex) {
                //那么将next位置的元素移动到i位置,next=i+1,即元素前移
                items[i] = items[next];
                //然后i=next,继续下一次循环
                i = next;
            }
            /*否则,表示元素移动完毕*/
            else {
                //将原来的队尾置空
                items[i] = null;
                //获取下一个插入元素的索引等于i,也相当于逻辑减少1,这里不用next-1是因为可能next为0
                this.putIndex = i;
                //结束循环
                break;
            }
        }
        //计数器自减
        count--;
        //类似于出队列的方法,调整迭代器链表的值
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    //唤醒一个在notFull上等待的生产者线程
    notFull.signal();
}
复制代码

2.5 检查操作

2.5.1 peek()方法

public E peek()

获取但不移除此队列的头;如果此队列为空,则返回 null。

/**
 * @return 获取但不移除此队列的头;如果此队列为空,则返回 null。
 */
public E peek() {
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        //返回底层数组指定takeIndex索引的元素
        return itemAt(takeIndex); // null when queue is empty
    } finally {
        //解锁
        lock.unlock();
    }
}

/**
 * 返回底层数组指定索引的元素
 */
@SuppressWarnings("unchecked")
final E itemAt(int i) {
    return (E) items[i];
}
复制代码

2.5.2 element()方法

public E element()

获取但是不移除此队列的头。此方法与 peek 唯一的不同在于此队列为空时将抛出一个异常。

/**
 * 获取,但是不移除此队列的头。此方法与 peek 唯一的不同在于:此队列为空时将抛出一个异常。
 *
 * @return 队头
 * @throws NoSuchElementException 如果此队列为空
 */
public E element() {
    //内部调用peek方法获取返回值x
    E x = peek();
    //如果x不为null,那么返回x;否则抛出NoSuchElementException异常
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
复制代码

2.6 size操作

public int size()

返回此队列中元素的数量。和其他大部分并发容器相比,这个方法需要先获取独占锁,因此返回的是精确值!

public boolean isEmpty()

如果此队列不包含元素,则返回 true。

/**
 * @return 返回此队列中元素的数量
 */
public int size() {
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        //返回count
        return count;
    } finally {
        //解锁
        lock.unlock();
    }
}

/**
 * @return 如果此集合不包含元素,则返回 true。
 */
public boolean isEmpty() {
    //直接调用的size方法
    return size() == 0;
}
复制代码

3 ArrayBlockingQueue的总结

ArrayBlockingQueue是一个底层使用逻辑环形数组实现的阻塞队列,其容量大小不能更改。它的属性均没有volatile修饰,内部只有一把全局独占锁lock,线程的入队、出队都需要获取该锁。另外具有两个条件队列notEmpty、notFull分别消费线程和生产线程在不满足条件的情况下挂起和唤醒(等待-通知)。

相比于其他的并发容器,它的size 操作的结果也是精确的,因为同样加了全局锁lock。但是它的缺点很明显,那就是由于只有一个全局独占锁,相当于给整个ArrayBlockingQueue对象都加了一把大锁,锁粒度太大,因此它的基本没什么并发性能了。

另外ArrayBlockingQueue的迭代器也非常有意思,它的内部维护了一个迭代器链表,并且结点还是弱引用,每次删除结点的时候如果takeIndex=0那就回调通知迭代器链表,更新迭代器的数据,但是实际上迭代器用的比较少,而且源码还是比较多的,在此也没有深入讲解,不过肯定是安全失败的,即支持并发修改!

ArrayBlockingQueue的源码非常简单,主要是因为对于线程的获取锁、释放锁、等待、唤醒等基本操作的源码都交给ReentrantLock、AQS和Concition来实现了,比如最开始说的公平和非公平模式,就是通过ReentrantLock的不同模式来实现的。如果想要搞定这些,那还需要花一定时间!

相关文章:

  1. AQS:JUC—五万字的AbstractQueuedSynchronizer(AQS)源码深度解析与应用案例
  2. ReentrantLock: JUC—ReentrantLock source code in-depth analysis .

If you don't understand anything or need to communicate, you can leave a message. In addition, I hope to like, favorite, and follow, I will continue to update various Java learning blogs!

Guess you like

Origin juejin.im/post/7085578037701378061