阻塞队列之ArrayBlockingQueue

ArrayBlockingQueue

基于数组实现的阻塞队列(FIFO)

一、类属性

	//存储数据的数组
	final Object[] items;

    /** 下次取的索引 */
    int takeIndex;

    /**下次存的索引*/
    int putIndex;

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

    /** 使用可重入锁保证线程安全 */
    final ReentrantLock lock;

    /**队列非空的条件队列*/
    private final Condition notEmpty;

    /** 队列非满的条件队列*/
    private final Condition notFull;


    transient Itrs itrs = null;

  1. 基于数组实现
  2. 两个条件队列可以看出类似生产者消费者模式
  3. 可重入锁保证线程安全
  4. 两个索引分别用于存和取

二、构造函数

//指定初始容量
public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

   //指定初始容量和ReentrantLock的公平还是非公平模式
    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();
    }

   //带集合
    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 {
                //遍历c添加到队列
                for (E e : c) {
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            //元素个数
            count = i;
            //元素个数达到容量,则存索引重置为0,否则为i
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

三、入队add()

入队操作:

  1. add()
  2. offer()
  3. 带超时时间的offer
  4. put()

此方法在不超过队列容量的情况下立即将指定的元素插入此队列的尾部,

  1. 成功返回true
  2. 如果队列满了,则抛出IllegalStateException异常
 public boolean add(E e) {
     //调用父类AbstractQueue的add方法
        return super.add(e);
    }
//AbstractQueue#add()
 public boolean add(E e) {
     	//调用ArrayBlockingQueue的offer方法
        if (offer(e))
            return true;
        else
            //队列满,抛异常
            throw new IllegalStateException("Queue full");
    }
3.1 offer

入队操作:

队列满,返回false

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

真正的入队操作:

 private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
     //在当前存放索引位置放入元素
        items[putIndex] = x;
     //如果+1后满了,重置为0
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
     //唤醒非空线程,可以取数据了
        notEmpty.signal();
    }
3.2 带超时的offer()

队列满,阻塞等待指定时间后,如果还满就返回false,否则元素入队

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)//如果到了超时时间,则返回false
                    return false;
                //如果没到超时时间,则阻塞等待nanos ns
                nanos = notFull.awaitNanos(nanos);
            }
            //如果队列没满,或等待nanos时间内有元素出队,则从上面的awaitNanos唤醒,执行入队操作
            enqueue(e);
            return true;
        } finally {
            lock.unlock();//释放锁
        }
    }

四、put()入队

也是入队,但是如果队列满了,会调用notFull.await()阻塞,直到被取出元素后再继续执行

 public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
     //可中断的加锁
        lock.lockInterruptibly();
        try {
            //如果队列满,则阻塞
            while (count == items.length)
                //非满条件队列阻塞
                notFull.await();
            //否则入队
            enqueue(e);
        } finally {
            //释放锁
            lock.unlock();
        }
    }

五、出队

5.1 take()
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();//可中断的上锁
        try {
            while (count == 0)//如果队列空
                notEmpty.await();//调用notEmpty等待
            return dequeue();//否则队尾元素出队
        } finally {
            //释放锁
            lock.unlock();
        }
    }

dequeue()
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;
    //如果取数据索引到达尾部,则重置为0
        if (++takeIndex == items.length)
            takeIndex = 0;
    //元素个数减一
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    //唤醒非满条件队列
        notFull.signal();
        return x;
    }
  1. 如果队列空,就调用notEmpty.await阻塞
  2. 否则出队,并唤醒非满条件队列,此时阻塞在put中的notFull.await()位置的线程会被唤醒
5.2 poll()

队列空返回null

 public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();//上锁
        try {
            //如果队列空就返回null,否则执行出队操作
            return (count == 0) ? null : dequeue();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

带超时时间的出队:

  1. 如果队列空,则等待timeout时间
  2. 如果等待过后还是空的队列,就返回null
  3. 如果等待后队列不空,说明有元素入队了,则执行出队操作
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)//等待nanos时间后还是空的就返回null
                    return null;
                //阻塞等待nanos时间
                nanos = notEmpty.awaitNanos(nanos);
            }
            //出队
            return dequeue();
        } finally {//释放锁
            lock.unlock();
        }
    }
5.3 peek()

返回takeIndex位置的元素

 public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();//上锁
        try {
            return itemAt(takeIndex); 
        } finally {
            lock.unlock();
        }
    }
 final E itemAt(int i) {
        return (E) items[i];
    }

六、remove(o)

删除指定对象

 public boolean remove(Object o) {
     //删除对象为空,返回false
        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);
            }
            //队列空,返回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;
        if (removeIndex == takeIndex) {//如果正好是取出索引
            // removing front item; just advance
            //直接置空
            items[takeIndex] = null;
            //取索引到头,重置为0循环
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;
            if (itrs != null)//Itr是ArrayBlockingQueue的迭代器类,而Itrs则是由Itr组成的链表集合类。因为循环数组和移除元素时会使迭代器丢失他们的位置,因此需要保证迭代器和队列数据的一致性
                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;//保存下一个索引
                //如果removeIndex到尾部,则重置为0
                if (next == items.length)
                    next = 0;
                if (next != putIndex) {//当下一个下标不是putIndex时
                    //从removeIndex开始全部向前移动一位
                    items[i] = items[next];
                    i = next;
                } else {
                    //否则置该位置为空
                    items[i] = null;
                    
                    this.putIndex = i;
                    break;
                }
            }
            count--;
            if (itrs != null)//迭代器保持一致
                itrs.removedAt(removeIndex);
        }
     //唤醒非满条件队列
        notFull.signal();
    }

总结

  • 结构:基于数组

  • 锁:ReentrantLock重入锁

  • 必须指定队列大小,且不能扩容

  • 数组可循环利用

  • 消费速度跟不上生产速度,会造成大量入队线程阻塞

  • add:队列满抛异常

  • offer:队列满返回false,入队后唤醒非空条件队列

  • 超时offer:队列满等待指定时间,如果还满就返回false,否则按offer的入队操作

  • put:队列满,调用非满队列阻塞

  • take:队列空,调用notEmpty等待,否则出队,并唤醒非满队列

  • poll:队列空返回null,否则执行出队

  • 超时poll:队列空,等待指定时间,如果还空,就返回null,否则执行出队

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值