ArrayBlockingQueue源码浅析

ArrayBlockingQueue位于java.util.concurrent并发包中,是从JDK1.5之后添加的。以下解析均基于目前最新的JDK8.0版本。

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable
从这里我们可以看出,ArrayBlockingQueue继承自AbstractQueue,并实现了BlockingQueue接口。我们可以将ArrayBlockingQueue拆分成三段,即Array,Blocking,Queue来分别进行解析。

首先是Array,我们可以看到在ArrayBlockingQueue中有一个字段如下:

/** The queued items */
    final Object[] items;

说明ArrayBlockingQueue的底层是用数组实现的。

下面再来看看如何解析Blocking,也就是ArrayBlockingQueue是如何实现阻塞的,看源码:

/*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;
也就是说ArrayBlockingQueue是通过ReentrantLock(可重入锁)和2个Condition(条件变量)来实现可阻塞的。以put操作为例:

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();
        }
    }
首先获取锁,之后在while循环中检测队列是否已满,如果已满,则调用notFull条件变量的await方法进行等待,如果未满,则调用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;
        count++;
        notEmpty.signal();
    }

前面部分都是正常的入队操作,关键是最后一句:notEmpty.signal(),这一句的作用就是唤醒那些在notEmpty条件变量上等待的线程,因为入队操作之前很可能队列是空的,所以如果在这之前有线程访问队列并试图进行take操作的话会进行阻塞。而一旦入队了,则应该对那些阻塞的线程进行唤醒。我们看到在上面的put操作中有notFull.await();很显然我们可以推断出在与put相对应的take操作中,如果队列为空的话会有notEmpty.await()操作。源码如下:

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

如果take操作的时候队列不空的话,则调用dequeue操作进行出队,我们可以推断,在与上面enqueue操作相对的dequeue操作中的最后面会发送队列未满的信号:notFull.signal()去唤醒阻塞在put操作中notFull.await()操作处的线程。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;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

重点关注倒数第二句:notFull.signal(),即出队之后通知其他阻塞在notFull条件变量上的线程,队列现在未满,你们可以继续往里面put了。

综上所述,ArrayBlockingQueue中的take操作和put操作利用了ReentrantLock和2个Condition来实现阻塞。抽象出来就是:

    Condition notEmpty;
	Condition notFull;
	take() {   //从队列中取
		while(队列为空) {
			notEmpty.await();  //可以理解为,我要一直阻塞到notEmpty条件得到满足
		}
		dequeue();  //出队操作
	}
	dequeue() {
		出队;
		notFull.signal();   //可以理解为,已经出队了一个元素,让notFull条件得到满足。
	}
	
	put() {   //往队列中添加
		while(队列已满) {
			notFull.await();   //可以理解为,我要一直阻塞到notFull条件得到满足。
		}
		enqueue();
	}
	enqueue() {
		入队;
		notEmpty.signal();   //可以理解为,已经有元素入队了,让notEmpty条件得到满足。
	}
上面伪代码省略了获取锁和释放锁的过程。关于Condition条件变量的含义,可能我理解的也不太准确,下次专门写一篇来学习一下Condition。

        已经解析完了ArrayBlockingQueue三部分中的前面两部分,最后解析一下Queue的部分,这部分没有太多好解析的,就是一个队列而已,有出队和入队操作,需要说明的是ArrayBlockingQueue中有多组出队入队操作,但是它们之间是有所不同的:

以下4个方法都是入队操作。

  • add(E e)操作,如果队列已满则会抛出异常
  • offer(E e) 操作,不会阻塞,立即返回,如果队列已满则返回false
  • offer(E e, long timeout, TimeUnit unit) 如果队列已满,会先阻塞一段时间,之后若还是满的,则返回false
  • put(E e) 如果队列满则会一直阻塞直到有空间可以入队。

以下三个方法都是出队操作。

  • poll()操作,与offer(E e) 操作相对应,不会阻塞,立即返回,如果队列为空则返回null,否则返回队首元素。
  • poll(long timeout, TimeUnit unit)与offer(E e, long timeout, TimeUnit unit)操作相对应,若队列为空则会阻塞一定时间,若一段时间后仍为空则返回null
  • take()操作与put(E e)操作相对应,会一直阻塞直到队列不为空。

小结一下:

  • take(出队)和put(入队)是一对
  • poll(出队)和offer(入队)是一对
  • poll(带时间参数)和offer(带时间参数)是一对
  • add(入队)


水平有限,如有不当之处,还望指正!



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值