Java并发之阻塞队列 ArrayBlockingQueue

1. 简介

ArrayBlockingQueue,一个由数组实现的有界阻塞队列。该队列采用 FIFO 的原则对元素进行排序添加的。

ArrayBlockingQueue 为有界且固定,其大小在构造时由构造函数来决定,确认之后就不能再改变了。

ArrayBlockingQueue 支持对等待的生产者线程和使用者线程进行排序的可选公平策略,但是在默认情况下不保证线程公平的访问,在构造时可以选择公平策略(fair = true)。公平性通常会降低吞吐量,但是减少了可变性和避免了“不平衡性”。

2. 构造方法

先看看 java.util.concurrent.ArrayBlockingQueue 的构造方法,代码如下:

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

    private static final long serialVersionUID = -817911632652898426L;
    
    final Object[] items;
    int takeIndex;
    int putIndex;
    int count;
    // 重入锁
    final ReentrantLock lock;
    // notEmpty condition
    private final Condition notEmpty;
    // notFull condition
    private final Condition notFull;
    transient ArrayBlockingQueue.Itrs itrs;
    
    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();
    }
    
}
  • 可以清楚地看到 ArrayBlockingQueue 继承 java.util.AbstractQueue ,实现
    java.util.concurrent.BlockingQueue 接口。看过 java.util 包源码的同学应该都认识AbstractQueue,该类在 java.util.Queue 接口中扮演着非常重要的作用,该类提供了对queue 操作的骨干实现(具体内容移驾其源码)。

  • java.util.concurrent.BlockingQueue 继承 java.util.Queue 接口,为阻塞队列的核心接口,提供了在多线程环境下的出列、入列操作。作为使用者,则不需要关心队列在什么时候阻塞线程,什么时候唤醒线程,所有一切均由 BlockingQueue 来完成。

  • ArrayBlockingQueue 内部使用可重入锁 ReentrantLock + Condition 来完成多线程环境的并发操作。

    • items 变量,一个定长数组,维护 ArrayBlockingQueue 的元素。
    • takeIndex 变量,int ,为 ArrayBlockingQueue 队首位置。
    • putIndex 变量,int ,ArrayBlockingQueue 队尾位置。
    • count 变量,元素个数。
  • lock 变量,ReentrantLock ,ArrayBlockingQueue 出列入列都必须获取该锁,两个步骤共用一个锁。

    • notEmpty 变量,非空,即出列条件。
    • notFull 变量,未满,即入列条件。

3. 入队

ArrayBlockingQueue 提供了诸多方法,可以将元素加入队列尾部。

  • add(E e) 方法:将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则抛出 IllegalStateException 异常。
  • offer(E e) 方法:将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则返回 false 。
  • offer(E e, long timeout, TimeUnit unit) 方法:将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
  • put(E e) 方法:将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。

3.1 add

// ArrayBlockingQueue.java
@Override
public boolean add(E e) {
    return super.add(e);
}

// AbstractQueue.java
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}
  • add(E e)方法,调用 #offer(E e) 方法,如果返回false,则直接抛出 IllegalStateException 异常。

3.2 offer

@Override
public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}
  • 首先,检查是否为 null 。
  • 然后,获取 Lock 锁。获取锁成功后,如果队列已满则,直接返回 false 。
  • 最后,调用 #enqueue(E e) 方法,它为入列的核心方法,所有入列的方法最终都将调用该方法,在队列尾部插入元素。

3.2.1 enqueue

private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    // 添加元素
    final Object[] items = this.items;
    items[putIndex] = x;
    // 到达队尾,回归队头
    if (++putIndex == items.length)
        putIndex = 0;
    // 总数+1
    count++;
    // 通知阻塞在出列的线程
    notEmpty.signal();
}
  • 该方法就是在 putIndex(对尾)位置处,添加元素,最后调用 notEmpty 的 #signal() 方法,通知阻塞在出列的线程(如果队列为空,则进行出列操作是会阻塞)。

3.3 可超时的 offer

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

    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 获得锁
    lock.lockInterruptibly();
    try {
        // <1> 若队列已满,循环等待被通知,再次检查队列是否非空
        while (count == items.length) {
            // 可等待的时间小于等于零,直接返回失败
            if (nanos <= 0)
                return false;
            // 等待,直到超时
            nanos = notFull.awaitNanos(nanos); // 返回的为剩余可等待时间,相当于每次等待,都会扣除相应已经等待的时间。
        }
        // 入队
        enqueue(e);
        return true;
    } finally {
        // 解锁
        lock.unlock();
    }
}
  • 相比 #offer(E e) 方法,增加了 <1> 处:
  • 若队列已满,调用 notFull 的 #awaitNanos(long nanos) 方法,等待被通知(元素出列时,会调用 notFull 的 #signal() 方法,进行通知阻塞等待的入列线程)或者超时。
  • 被通知后,再次检查队列是否非空。若非空,继续向下执行,否则继续等待被通知。

3.4 put

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 获得锁
    lock.lockInterruptibly();
    try {
        // <1> 若队列已满,循环等待被通知,再次检查队列是否非空
        while (count == items.length)
            notFull.await();
        // 入队
        enqueue(e);
    } finally {
        // 解锁
        lock.unlock();
    }
}
  • 相比 #offer(E e) 方法,增加了 <1> 处:
  • 若队列已满,调用 notFull 的 #await() 方法,等待被通知(元素出列时,会调用 notFull 的 #await() 方法,进行通知阻塞等待的入列线程)。
    • 被通知后,再次检查队列是否非空。若非空,继续向下执行,否则继续等待被通知。

4. 出队

ArrayBlockingQueue 提供的出队方法如下:

  • poll() 方法:获取并移除此队列的头,如果此队列为空,则返回 null 。
  • poll(long timeout, TimeUnit unit) 方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
  • take() 方法:获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
  • remove(Object o) 方法:从此队列中移除指定元素的单个实例(如果存在)。

4.1 poll

public E poll() {
    // 获得锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 获得头元素
        return (count == 0) ? null : dequeue();
    } finally {
        // 释放锁
        lock.unlock();
    }
}
  • 如果队列为空,则返回 null,否则,调用 #dequeue() 方法,获取列头元素。

3.1.1 dequeue

 private E dequeue() {
    final Object[] items = this.items;
    // 去除队首元素
    E x = (E) items[takeIndex];
    items[takeIndex] = null; // 置空
    // 到达队尾,回归队头
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 总数 - 1
    count--;
    // 维护下迭代器
    if (itrs != null)
        itrs.elementDequeued();
    // 通知阻塞在入列的线程
    notFull.signal();
    return x;
}
  • 该方法主要是从列头(takeIndex 位置)取出元素,同时如果迭代器 itrs 不为 null ,则需要维护下该迭代器。最后,调用 notFull 的 #signal() 方法,唤醒阻塞在入列线程。

4.2 可超时的 poll

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 获得锁
    lock.lockInterruptibly();
    try {
        // <1> 若队列已空,循环等待被通知,再次检查队列是否非空
        while (count == 0) {
            // 可等待的时间小于等于零,直接返回 null
            if (nanos <= 0)
                return null;
            // 等待,直到超时
            nanos = notEmpty.awaitNanos(nanos); // 返回的为剩余可等待时间,相当于每次等待,都会扣除相应已经等待的时间。
        }
        // 出队
        return dequeue();
    } finally {
        // 解锁
        lock.unlock();
    }
}
  • 相比 #poll() 方法,增加了 <1> 处:
  • 若队列已空,调用 notEmpty 的 #awaitNanos(long nanos) 方法,等待被通知(元素入列时,会调用 notEmpty 的 #signal() 方法,进行通知阻塞等待的出列线程)或者超时返回 null。
  • 被通知后,再次检查队列是否为空。若非空,继续向下执行,否则继续等待被通知。

4.3 take

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 获得锁
    lock.lockInterruptibly();
    try {
        // <1> 若队列已空,循环等待被通知,再次检查队列是否非空
        while (count == 0)
            notEmpty.await();
        // 出列
        return dequeue();
    } finally {
        // 解锁
        lock.unlock();
    }
}

相比 #poll() 方法,增加了 <1> 处:
若队列已空,调用 notEmpty 的 #await() 方法,等待被通知(元素入列时,会调用 notEmpty 的 #signal() 方法,进行通知阻塞等待的出列线程)。
被通知后,再次检查队列是否为空。若非空,继续向下执行,否则继续等待被通知。

4.4 remove

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;
                }
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
        }
        return false;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

详细解析,见代码注释。

  • removeAt(int removeIndex) 方法,移除指定位置的元素。代码如下:
void removeAt(final int removeIndex) {
    // assert lock.getHoldCount() == 1;
    // assert items[removeIndex] != null;
    // assert removeIndex >= 0 && removeIndex < items.length;
    final Object[] items = this.items;
    // 移除的为队头,直接移除即可
    if (removeIndex == takeIndex) {
        // removing front item; just advance
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    // 移除非队头,移除的同时,需要向前复制,填补这个空缺。
    } else {
        // an "interior" remove

        // slide over all others up through putIndex.
        final int putIndex = this.putIndex;
        for (int i = removeIndex;;) {
            int next = i + 1;
            if (next == items.length)
                next = 0;
            if (next != putIndex) {
                items[i] = items[next];
                i = next;
            } else {
                items[i] = null;
                this.putIndex = i;
                break;
            }
        }
        count--;
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    // 通知
    notFull.signal();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值