心血来潮,想了解一下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技术小馆“,如果文章对您有帮助,欢迎关注我的公众号。