来谈谈 BlockingQueue 阻塞队列实现类 java.util.concurrent.ArrayBlockingQueue(JDK1.8 源码分析)

ArrayBlockingQueue 源码刨析



前言

public class ArrayBlockingQueue extends AbstractQueue
implements BlockingQueue, java.io.Serializable

AbstractQueue:父类为Queue
BlockingQueue:阻塞队列接口
Serializable : 序列化标记接口


提示:以下是本篇文章正文内容,下面案例可供参考

一、ArrayBlockingQueue 源码部分

1.构造方法

public ArrayBlockingQueue(int capacity) { //capacity指定队列的大小(capacity是必须的)
    this(capacity, false);
}

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0) //队列大小设为<=0是抛出非法算术异常
        throw new IllegalArgumentException();
    this.items = new Object[capacity]; //创建一个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(); // 加锁
    try {
        int i = 0;
        try {
            for (E e : c) { //循环集合
                checkNotNull(e); //检查元素是否为null
                items[i++] = e; //将元素加入到队列数组中去
            }
        } catch (ArrayIndexOutOfBoundsException ex) { //数组越界异常(超出队列大小)
            throw new IllegalArgumentException();
        }
        count = i; //队列中元素的个数
        putIndex = (i == capacity) ? 0 : i; //队列下一个放入元素的位置
    } finally {
        lock.unlock(); //释放锁
    }
}

2.成员变量

//成员变量
final Object[] items; //队列存储元素的数组

int count; //队列中元素的个数

int putIndex; //下一个入队的位置(队尾的下一个)

int takeIndex; //下一个取出元素的位置(队首)

transient Itrs itrs = null;

final ReentrantLock lock; //全局锁

private final Condition notEmpty;

private final Condition notFull;

3.主要方法

1.入队操作

public boolean add(E e) {
    return super.add(e);
}

//AbstractQueue的add方法
public boolean add(E e) { //add方法在队列满时抛出异常
    if (offer(e)) //调用一个参数的offer方法,该方法在队列满时直接直接返回false
        return true;
    else
        throw new IllegalStateException("Queue full"); //队列满时抛出非法状态异常
}

与Queue接口中规范的相同当队列为满时直接抛出异常


//该方法在队列为满时直接返回false不阻塞也不抛异常
public boolean offer(E e) {
    checkNotNull(e); //检查e是否为null,队列中不可以有空值
    final ReentrantLock lock = this.lock;
    lock.lock(); //加锁
    try {
        if (count == items.length) //队列已满
            return false; //直接返回false(没有阻塞)
        else {
            enqueue(e); //队列中插入元素操作
            return true;
        }
    } finally {
        lock.unlock(); //释放锁
    }
}

//检查加入队列元素是否为空
private static void checkNotNull(Object v) {
    if (v == null)
        throw new NullPointerException(); //加入队列元素为空时,抛出空指针异常
}

//插入一个元素到队列中去(只有在持有锁时才被调用)
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items; //获取队列数组
    items[putIndex] = x; //放入元素在putIndex位置
    if (++putIndex == items.length) //已经到了队列的末尾,将下一个元素的位置设置0
        putIndex = 0;           //可见队列是一个循环队列
    count++;  //队列元素加1
    notEmpty.signal(); //向队列添加元素成功唤醒那些阻塞在出队操作上等待的线程
}

//该方法在offer方法上指定了超时时间(线程中断时抛出中断异常)
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    checkNotNull(e); //入队元素不能为null(为null时直接抛出运行时异常NullPointerException)
    long nanos = unit.toNanos(timeout);//转换成纳秒
    final ReentrantLock lock = this.lock; 
    lock.lockInterruptibly();//加锁操作
    try {
        while (count == items.length) { //如果队列已满
            if (nanos <= 0) //之前设置的timeout小于0队列满时直接返回false
                return false;
            nanos = notFull.awaitNanos(nanos); //线程等待指定时间(此时释放所持有的锁)
        }
        enqueue(e); //队列未满时直接入队
        return true; //入队成功后返回true
    } finally {
        lock.unlock(); //释放锁
    }
}

1.与Queue中接口规范的相同offer(E e)方法在队列满时会直接返回false,否则返回true。
2.与BlockingQueue中接口规范的相同 offer(E e, long timeout, TimeUnit unit) 可以阻塞指定的时间(阻塞过程可以被唤醒或被中断),超过时间队列任然为满时直接返回false,否则返回true。
3.入队操作需要先获得一把全局锁。
4. offer(E e, long timeout, TimeUnit unit) 阻塞时线程进入等待队列 notFull 等待被唤醒,此时会释放掉锁持有的锁。
5. 队列中加入的元素不能有null,否则会抛异常


public void put(E e) throws InterruptedException {
    checkNotNull(e); //检查插入队列元素是为null,null时抛出空指针异常
    final ReentrantLock lock = this.lock; //获取全局锁
    lock.lockInterruptibly(); //加锁
    try {
        while (count == items.length) //队列满时将会一直阻塞
            notFull.await();
        enqueue(e); //向队列中插入一个元素
    } finally {
        lock.unlock(); //释放锁
    }
}

与BlockingQueue中接口规范的相同队列为满时直接阻塞线程进入等待队列,直到被唤醒或者被线程中断。

2.出队操作

//将指定元素从队列中移除
public boolean remove(Object o) {
    if (o == null) return false; //指定对象为空时直接返回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 { //循环遍历队列数组(遍历到putIndex这个位置)
                if (o.equals(items[i])) { //找到要删除的对象
                    removeAt(i); //删除
                    return true; //返回真
                }
                if (++i == items.length) //已经到了数组的尾部
                    i = 0; //回到数组开头
            } while (i != putIndex);
        }
        return false; //队列中没有元素时或没有找到要删除的元素时直接返回false
    } finally {
        lock.unlock();//释放锁
    }
}

//根据下标删除队列中第几个元素
void removeAt(final int removeIndex) {
    // assert lock.getHoldCount() == 1; //断言已获取全局锁
    // assert items[removeIndex] != null;//断言要删除的元素不为null
    // assert removeIndex >= 0 && removeIndex < items.length; //删除元素下标在数组范围内
    final Object[] items = this.items; //获取队列数组
    if (removeIndex == takeIndex) { //要删除的元素是队首
        // removing front item; just advance
        items[takeIndex] = null; //将队首的值设置为null
        if (++takeIndex == items.length) //队首元素变为下一位
            takeIndex = 0;
        count--; //队列中元素减一个
        if (itrs != null) //共享迭代器不为空
            itrs.elementDequeued();// ?
    } else { //删除元素不是队首的情况(删除元素在队列中间需要元素的移动)
        // an "interior" remove

        // 把从删除位置的元素位置之后的元素都向前移动一个位置
        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();//唤醒那些阻塞在等待队列 notFull 上的线程(告诉他们可以进行入队了)
}

1.remove(Object o)方法删除指定的元素,进行删除操作的元素不能为null,并且删除操作之前必须先获得全局锁。
2.如果删除的元素不是队首还需要进行数组元素的移动(前移),比较耗时。

//出队操作,队列为空时返回null,队列不为空时放回队列队首
public E poll() {
    final ReentrantLock lock = this.lock; 
    lock.lock(); //加锁操作
    try {
        return (count == 0) ? null : dequeue(); //队列为空时返回空
    } finally {
        lock.unlock(); //释放锁
    }
}
//拿出队首的元素该方法只有在获取锁时被调用
private E dequeue() {
    // assert lock.getHoldCount() == 1; //断言已经获取全局锁
    // assert items[takeIndex] != null; //断言队首有元素
    final Object[] items = this.items; //获取队列数组引用
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex]; //获取队首元素
    items[takeIndex] = null; //队首元素设置为空
    if (++takeIndex == items.length) //更新队首下标
        takeIndex = 0;
    count--; //队列中的元素减一
    if (itrs != null) //
        itrs.elementDequeued();
    notFull.signal(); //唤醒阻塞在入队操作上的线程
    return x;
}
//获取队列中元素,可设置超时时间,队列中没有元素时将被阻塞,直到超时或者中断
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(); //释放锁
    }
}

1.与Queue接口规范的相同poll()方法在队列为空时直接返回null
2.与BlockingQueue中接口规范相同poll(long timeout, TimeUnit unit)可以阻塞指定时间直到被唤醒或者被线程中断。

//该方法在队列中取出元素,队列为空时阻塞,直到队列中含有元素时。可被中断
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly(); //加锁操作
    try {
        while (count == 0) //队列为中不含元素时
            notEmpty.await(); //阻塞
        return dequeue();
    } finally {
        lock.unlock(); //释放锁
    }
}

与BlockingQueue中接口规范的相同take方法在队列为空时将一直阻塞在等待队列上,直到被唤醒或线程中断。

public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock(); //加锁
    try {
        return itemAt(takeIndex); //当队列为为空时返回null
    } finally {
        lock.unlock(); //释放锁
    }
}

//直接返回队首元素,队列为空时队首元素也为空即items[0] = null
final E itemAt(int i) {
    return (E) items[i];
}

与Queue中接口规范的相同peek()返回 队首元素(不移除),为空时返回null。

总结

入队描述
add(E e)方法加入元素到队尾队列满时直接抛出异常
offer(E e)方法加入元素到队尾队列满时直接返回false
put(E e)方法往队首加入元素,队列满时会一直阻塞,直到队列有空位置时。该方法可被中断。
offer(E e, long timeout, TimeUnit unit)方法加入元素到队尾队队列满时,1,如果timeout<0将直接返回false,2,timeout>0时会等待timeout时间,超时队列仍为满时直接返回false,3,该方法被中断,将抛出中断异常,4,线程未被中断的情况下,队列未满时入队后直接返回true 或 着队列为满时timeout时间内队列不满了入队后直接返回true
出队描述
remove(Object o)方法删除队列中指定元素对象,失败时返回直接返回false,成功返回true,删除过程涉及数组元素的向前移动。
poll()方法返回队首元素,队列为null时直接返回null。
poll(long timeout, TimeUnit unit)方法与上面的入队操作的offer方法类似。
take()方法与上面的put方法类似。
peek()方法返回队首元素,队列为null时直接返回null。(注意:peek方法不会移除队首的元素只是单纯的返回队首的元素)

对于阻塞队列我们使用put()和take()方法进行阻塞操作,当然我们也可以通过offer和poll设置超时时间进行阻塞。其他方法add,offer(E e),remove,poll(),peek()等入队和出队失败后就直接返回了或抛异常。

结束:

  1. ArrayBlockingQueue底层使用了一把全局锁(可以传入Reentrant对象来控制全局锁的公平性),入队和出队操作都使用了该锁,所以一个时间只能有一个操作,不能同时入队或出队,因此不支持生产者消费者模式。
  2. ArrayBlockingQueue底层使用了两个Condition (notFull和notEmpty),当队列满时或空时进行等待。
  3. 调用remove(Object o)方法时可能涉及到元素的迁移,比较耗时。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值