Java集合类学习--ArrayBlockingQueue

概述

ArrayBlockingQueue是下一个以数组实现的阻塞队列,它是线程安全的。是BlockingQueue接口的一个主要实现类,本文分析该类的常用方法实现。

ArrayBlockingQueue 的类继承结构如下:
在这里插入图片描述

源码分析

构造器

// 构造器 1:初始化 ArrayBlockingQueue 对象,使用给定的容量
public ArrayBlockingQueue(int capacity) {
    // 调用构造器 2 进行初始化,默认使用非公平锁
    this(capacity, false);
}
// 构造器 2:使用给定容量及是否公平初始化 ArrayBlockingQueue 对象
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    // 用给定的容量初始化内部数组
    this.items = new Object[capacity];
    // 创建锁对象(根据 fair 参数确定是否公平锁)
    lock = new ReentrantLock(fair);
    // lock 绑定两个 Condition 条件
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
// 构造器 3:使用给定的容量、是否公平,及给定的集合初始化 ArrayBlockingQueue
public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
    // 使用构造器 2 初始化 ArrayBlockingQueue 对象
    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;
        // 入队操作(put、offer 等方法)的数组下标,若数组已满则为 0
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        // 注意释放锁
        lock.unlock();
    }
}

通过构造方法我们可以得出以下两个结论:
(1)ArrayBlockingQueue初始化时必须传入容量,也就是数组的大小;
(2)可以通过构造方法控制重入锁的类型是公平锁还是非公平锁;

重要属性

/** 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.
 * 双条件(notEmpty、notFull)算法用于并发控制
 */

/** Main lock guarding all access */
// 使用 ReentrantLock 保证线程安全
final ReentrantLock lock;

/** Condition for waiting takes */
// 等待 take 操作(消费)的条件
private final Condition notEmpty;

/** Condition for waiting puts */
// 等待 put 操作(生产)的条件
private final Condition notFull;

入队方法
1、add(E)

public boolean add(E e) {
    // 调用父类 AbstractQueue 的 add 方法
    return super.add(e);
}

// AbstractQueue 的 add 方法
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

add(E) 方法调用了父类 AbstractQueue 的 add(E) 方法,可以看到,实际上还是调用了 offer(E) 方法。因此 add(E) 和 offer(E) 实现基本是一致的,下面分析 offer(E) 方法。

2、offer(E), offer(E, timeout, Unit)

public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 若队列已满,立即返回 false
        if (count == items.length)
            return false;
        else {
            // 入队
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

enqueue 方法:

// 入队操作
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    items[putIndex] = x;
    // 若队列已满,则下标置为 0
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    // 唤醒 notEmpty 条件下等待的线程
    notEmpty.signal();
}

offer(E, timeout, Unit) 方法操作与 offer(E) 类似,只是多了超时等待,如下:

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 {
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

3、put(E) 方法

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 如果数组满了,使用notFull等待
        // notFull等待的意思是说现在队列满了
        // 只有取走一个元素后,队列才不满
        // 然后唤醒notFull,然后继续现在的逻辑
        // 这里之所以使用while而不是if
        // 是因为有可能多个线程阻塞在lock上
        // 即使唤醒了可能其它线程先一步修改了队列又变成满的了
        // 这时候需要再次等待
        while (count == items.length)
            notFull.await();
        // 入队
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

put(E) 也是将一个元素入队:若队列已满,则 notFull 条件下的线程等待。

以“生产者-消费者”模型类比,就是容器已满,生产者等待;否则执行入队,并唤醒消费者。

入队方法小结:
add(E): 入队成功返回 true,否则抛出 IllegalStateException 异常;
offer(E): 入队成功返回 true,失败返回 false;
offer(E, timeout, Unit): 同 offer(E),加了超时等待;
put(E): 无返回值,队列满的时候等待。

出队方法
1、poll()

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 队列为空时返回 null,否则将 takeIndex 位置元素出队
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

2、take(E)

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 队列为空时等待
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

3、poll(E, unit)

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)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}

上述三个方法都使用 dequeue 方法进行出队,如下:

// 出队操作
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;
    // 队列已经空了
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    // 迭代器操作用到,本文暂不深入分析
    if (itrs != null)
        itrs.elementDequeued();
    // 队列已经不满(not full)了,可以继续生产
    notFull.signal();
    return x;
}

4、peek()

public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return itemAt(takeIndex); // null when queue is empty
    } finally {
        lock.unlock();
    }
}

// 返回数组中指定位置的元素
final E itemAt(int i) {
    return (E) items[i];
}

出队方法小结:
poll(): 获取队列头部元素,并将其移除,队列为空时返回 null;
take(): 获取队列头部元素,并将其移除,队列为空时阻塞等待;
poll(long, unit): 获取队列头部元素,并将其移除,队列为空时等待一段时间,若超时返回 null;
peek(): 获取队列头部元素,但不移除该元素

小结

(1)ArrayBlockingQueue不需要扩容,因为是初始化时指定容量,并循环利用数组;
(2)ArrayBlockingQueue利用takeIndex和putIndex循环利用数组;
(3)入队和出队各定义了四组方法为满足不同的用途;
(4)利用重入锁和两个条件保证并发安全;
(5)ArrayBlockingQueue 是基于数组的阻塞队列实现,它在初始化时需要指定容量;
(6)内部使用了 ReentrantLock 保证线程安全;
(7)常用方法:
入队:add, offer, put
出队:poll, take, peek

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值