阻塞队列:典型的生产者、消费者模式;
入队线程可以理解为:生产者线程
出队线程可以理解为:消费者线程
简介
基于数组实现的,有界的,FIFO顺序的队列;
应用场景:Springcloud-Eureka三级缓存、Nacos、RocketMq【待研究】
类图
实现了BlockingQueue;继承了AbstractQueue;
内部类
-
Itr【待研究】
-
Itrs【待研究】
- Node : Node extends WeakReference【待研究】
成员
// 队列中当前的元素个数
int count;
// 队列中存放元素的数组,初始化时会指定大小
final Object[] items;
// 保证同时只有一个线程操作队列。ReentrantLock:可重入、互斥锁
final ReentrantLock lock;
// 可以理解为消费者等待队列
// 消费者 从队列取数据时(take、pull),如果队列为空,将请求放到该条件队列中,阻塞线程
private final Condition notEmpty;
// 可以理解为生产者等待队列
// 生产者 向队列放数据时(put、offer),如果队列满了,将请求放到该条件队列中,阻塞线程;
private final Condition notFull;
// 元素出队时的索引,take、poll、peek、remove
// takeIndex与putIndex配合保证了元素的FIFO
int takeIndex;
// 元素入队时的索引,put、offer、add
// takeIndex与putIndex配合保证了元素的FIFO
int putIndex;
方法
- 构造方法
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
// 初始化装元素的数组,大小为初始化指定的
this.items = new Object[capacity];
// 创建ReentrantLock锁,fair默认是false,非公平锁
lock = new ReentrantLock(fair);
// 队列为空时的,将消费者线程放到条件等待队列中,阻塞
notEmpty = lock.newCondition();
// 队列满时,将生产者线程放到条件等待队列中,阻塞
notFull = lock.newCondition();
}
- put
/*
* 向队列中放元素:
* 如果队列满了,会将线程放到生产者队列阻塞;
* 元素添加成功后,唤醒消费者队列;
*/
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
// 尝试加锁,线程需要非中断状态
lock.lockInterruptibly();
try {
// 如果当前已存在元素数 = 已存在最大元素个数
while (count == items.length)
// 将本次请求放到生产者等待队列,阻塞线程,等消费者唤醒
notFull.await();
// 元素入队
enqueue(e);
} finally {
lock.unlock();
}
}
- enqueue
private void enqueue(E e) {
// 元素赋值
final Object[] items = this.items;
items[putIndex] = e;
// putIndex自增,保证放元素的顺序性;防止索引越界,如果下一元素大小=数组length,则归0
if (++putIndex == items.length) putIndex = 0;
count++;
// 唤醒消费者线程
notEmpty.signal();
}
------------------------------------------
第一次put: putIndex:0,放到数组第0个元素位
第二次put: putIndex:1,放到数组第1个元素位
第三次put: putIndex:2,放到数组第2个元素位
- take
/*
* 从队列中取元素:
* 如果队列为空,会将线程放到消费者队列阻塞;
* 元素获取成功后,唤醒生产者队列;
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 尝试加锁,线程需要非中断状态
lock.lockInterruptibly();
try {
// 如果队列为空
while (count == 0)
// 将本次请求放到消费者等待队列,阻塞线程,等生产者唤醒
notEmpty.await();
// 元素出队
return dequeue();
} finally {
lock.unlock();
}
}
- dequeue
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
// 获取元素
E x = (E) items[takeIndex];
// 清空元素原有位置
items[takeIndex] = null;
// takeIndex自增,保证取元素的顺序性;防止索引越界,如果下一元素大小=数组length,则归0
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null) 【待研究】
itrs.elementDequeued();
// 唤醒生产者
notFull.signal();
return x;
}
------------------------------------------
第一次take: takeIndex:0,取数组第0个元素
第二次take: takeIndex:1,取数组第1个元素
第三次take: takeIndex:2,取数组第2个元素
问题
ArrayQlckingQueue入队方法区别
| 入队方法 | 返回值 | 特点 |
|---|---|---|
| put(E e) | void | 队列满了会阻塞线程,等待被消费者唤醒,直到队列不满时,再添加 |
| offer(E e) | boolean | 队列满了直接返回false,添加成功后返回true |
| offer(E e, long timeout, TimeUnit unit) | boolean | 队列满了会阻塞指定的时间(timeout), 如果还是满的返回false,添加成功后返回true |
| add | boolean | 调用offer,添加成功返回true,失败抛出异常 |
ArrayQlckingQueue出队方法区别
| 出队方法 | 返回值 | 特点 |
|---|---|---|
| take | E e | 队列为空会阻塞线程,等待被生产者唤醒,直到队列不为空时,再获取; 从队列中删除获取到的元素; |
| poll | E e | 队列为空直接返回null; 从队列中删除获取到的元素; |
| poll( long timeout, TimeUnit unit) | E e | 队列为空会阻塞指定的时间(timeout),如果还是空返回null; 从队列中删除获取到的元素; |
| peek | E e | 队列为空不会阻塞线程; 不会从队列中删除获取到的元素; |
| remove(Object o) | boolean | 从队列中删除o元素(equals比较),true代表成功,false代表不存在 |
WeakReference作用?Itrs.Node 为什么要用它修饰?
【待研究】
迭代器在ArrayBlockingQueue中的实现,Itrs、Itr
【待研究】