一文详解 BlockingQueue使用及实现原理

心血来潮,想了解一下BlockingQueue的实现原理。
我们都知道Queue队列中包含以下几个方法,具体如下:

// 源码来自:android-25/java/util/Queue
public interface Queue<E> extends Collection<E> {
	// 向队列中添加一个元素。若添加成功则返回true;若因为容量限制添加失败则返回false是。
    boolean offer(E e);
    // 删除队列头的元素,如果队列为空,则返回null;
    E poll();
    // 返回队列头的元素,如果队列为空,则返回null;
    E peek();
	
		// 向队列中添加一个元素。若添加成功则返回true;若因为容量限制添加失败,则抛出IllegalStateException异常。
    boolean add(E e);
	    // 删除队列头的元素,如果队列为空,则抛出异常;
    E remove();
	    // 返回队列头的元素,如果队列为空,将抛异常;
    E element();
}

这里以Android源码中ArrayBlockingQueue的实现源码举例进行说明:

  • 使用方式
  • 源码阅读

一、使用方式

BlockingQueue queue = new ArrayBlockingQueue(3);
// 向队列中插入数据(如果队列已满,阻塞当前线程,等待释放空间后再添加)
queue.put(1);
// 从队列中取出数据(如果队列为空,阻塞当前线程,等待有数据后再取)
queue.take();

二、源码阅读

注:源码来自android-25

2.1、构造方法:

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 的源码实现中用到了ReentrantLock(重入锁),对其使用方式不太了解的同学可以查看:
ReentrantLock(重入锁)使用方式

2.2、向ArrayBlockingQueue中插入数据:

public void put(E e) throws InterruptedException {
	// 判空
    Objects.requireNonNull(e);
    // 重入锁对象
    final ReentrantLock lock = this.lock;
    // 获取可中断锁
    lock.lockInterruptibly();
    //
    try {
	    // 如果队列已满,阻塞当前线程,等待队列释放出空间后的 notFull.signal() 信号(队列满了!!!)
        while (count == items.length){
	        notFull.await();
        }
        // 如果队列未满,则将数据插入队列中
        enqueue(e);
    } finally {
	    // 释放锁
        lock.unlock();
    }
}

// 向队列中插入数据
private void enqueue(E x) {
    // 数组构造的队列
    final Object[] items = this.items;
    // 数据添加到队列末尾
    items[putIndex] = x;
    // putIndex +1 与数组长度相同
    if (++putIndex == items.length) {
	    // index 归零,下次从index为0插入数据
	    putIndex = 0;
    }
    // 队列中item数量
    count++;
    // 发送数组不为空的信号,唤醒对应的线程(有数据了!!!)
    notEmpty.signal();
}

  • 向队列中插入数据时,如果队列已经满了,则调用notFull.await()让线程进入等待状态;直到队列中取出数据notFull.signal()信号发出后,才能重新唤醒添加数据的线程。
  • 向队列中插入数据时,不断向后边插入数据。如果当前插入的数据,是数组的最后一条数据,(只要数组不满)则下次从index为0的数组位置继续依次插入数据,从而形成一个循环。

2.3、向ArrayBlockingQueue中出队列:

public E take() throws InterruptedException {
	// 重入锁
    final ReentrantLock lock = this.lock;
    // 获取可中断锁
    lock.lockInterruptibly();
    //
    try {
	    // 如果数组中没有数据,阻塞当前线程,进入等待状态,等待notEmpty.signal()信号(没数据了!!!)
        while (count == 0){
	        notEmpty.await();
        }
        // 如果数组中有数据 出队列
        return dequeue();
    } finally {
	    // 释放锁
        lock.unlock();
    }
}

// 从队列中取出数据
private E dequeue() {
	// 数组
    final Object[] items = this.items;
    // 顺序取出数据
    E x = (E) items[takeIndex];
    // 数组置空
    items[takeIndex] = null;
    // 如果取出的数据是 数组的最后一条 下次从头开始取数据
    if (++takeIndex == items.length) {
	    takeIndex = 0;
    }
    // 队列中数据的数量减少一个
    count--;
    // 忽略...
    if (itrs != null){
	    itrs.elementDequeued();
    }
    // 发送 数据不为空 的信号(队列有空间了!!!)
    notFull.signal();
    return x;
}

  • 出队列时,如果队列已经没有数据了,则调用notEmpty.await()让线程进入等待状态;直到线程中加入数据notEmpty.signal()信号发出后,才能唤醒取数据的线程。
  • 出队列时,不断从前向后取。如果当前取出的数据,是数组的最后一条数据,(只要数组不空)则下次从index为0的数组位置继续依次取数据,从而形成一个循环。

= THE END =

文章首发于公众号”CODING技术小馆“,如果文章对您有帮助,欢迎关注我的公众号。
欢迎关注我的公众号

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bjxiaxueliang

您的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值