ArrayBlockingQueue-学习

144 阅读1分钟

阻塞队列:典型的生产者、消费者模式;

入队线程可以理解为:生产者线程

出队线程可以理解为:消费者线程

简介

基于数组实现的,有界的,FIFO顺序的队列;

应用场景:Springcloud-Eureka三级缓存、Nacos、RocketMq【待研究】

类图

image.png

实现了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
addboolean调用offer,添加成功返回true,失败抛出异常

ArrayQlckingQueue出队方法区别

出队方法返回值特点
takeE e队列为空会阻塞线程,等待被生产者唤醒,直到队列不为空时,再获取; 从队列中删除获取到的元素;
pollE e队列为空直接返回null; 从队列中删除获取到的元素;
poll( long timeout, TimeUnit unit)E e队列为空会阻塞指定的时间(timeout),如果还是空返回null; 从队列中删除获取到的元素;
peekE e队列为空不会阻塞线程; 不会从队列中删除获取到的元素;
remove(Object o)boolean从队列中删除o元素(equals比较),true代表成功,false代表不存在

WeakReference作用?Itrs.Node 为什么要用它修饰?

【待研究】

迭代器在ArrayBlockingQueue中的实现,Itrs、Itr

【待研究】

参照:blog.csdn.net/wjf25ac/art…