ArrayBlockingQueue源码分析

整体设计

在这里插入图片描述
ArrayBlockingQueue 继承了阻塞队列 BlockingQueue 这个接口,不难猜出通过继承 BlockingQueue 这个接口之后,ArrayBlockingQueue 就拥有了阻塞队列那些常见的操作行为。

同时, ArrayBlockingQueue 还继承了 AbstractQueue 这个抽象类,这个继承了 AbstractCollection 和 Queue 的抽象类,从抽象类的特定和语义我们也可以猜出,这个继承关系使得 ArrayBlockingQueue 拥有了队列的常见操作。

所以我们是否可以得出这样一个结论,通过继承 AbstractQueue 获得队列所有的操作模板,其实现的入队和出队操作的整体框架。然后 ArrayBlockingQueue 通过继承 BlockingQueue 获取到阻塞队列的常见操作并将这些操作实现,填充到 AbstractQueue 模板方法的细节中,由此 ArrayBlockingQueue 成为一个完整的阻塞队列。

初始化

// capacity 表示队列初始容量,fair 表示 锁的公平性
public ArrayBlockingQueue(int capacity, boolean fair) {
  //如果设置的队列大小小于0,则直接抛出IllegalArgumentException
  if (capacity <= 0)
      throw new IllegalArgumentException();
  //初始化一个数组用于存放队列的元素
  this.items = new Object[capacity];
  //创建阻塞队列流程控制的锁
  lock = new ReentrantLock(fair);
  //用lock锁创建两个条件控制队列生产和消费
  notEmpty = lock.newCondition();
  notFull =  lock.newCondition();
}

核心变量notEmpty、notFull

阻塞式获取和新增元素

ArrayBlockingQueue阻塞式获取和新增元素对应的就是生产者-消费者模型,对应的方法为

  • put(E e): 将元素插入队列中,如果队列已满,则该方法会一直阻塞,直到队列有空间可用或者线程被中断。
  • take(): 获取并移除队列头部元素,如果队列为空,则该方法会一直阻塞,直到队列非空或者被线程中断。

运行机制
在这里插入图片描述
说明:假设消费者先启动,当他发现队列中没有数据,那么非空条件就会将这个线程挂起,然后CPU执行权到达生产者,生产者发现队列中可以存放数据,于是将数据存放进去,通知此时条件非空,此时消费者就会被唤醒到队列中使用take方法获取值。
在这里插入图片描述
说明:生产者生产的速度远远大于消费者消费的速度,于是生产者将队列塞满后再次尝试将数据存入队列,发现队列已满,于是阻塞队列就将当前线程挂起,等待非满。然后消费者拿着CPU执行权进行消费,于是队列可以存放新数据。发出一个非满的通知,此时挂起的生产者就会等待CPU执行权到来时再次尝试将数据存到队列中。

put操作源码

public void put(E e) throws InterruptedException {
    //确保插入的元素不为null
    checkNotNull(e);
    //加锁
    final ReentrantLock lock = this.lock;
    //这里使用lockInterruptibly()方法而不是lock()方法是为了能够响应中断操作,如果在等待获取锁的过程中被打断则该方法会抛出InterruptedException异常。
    lock.lockInterruptibly();
    try {
            //如果count等数组长度则说明队列已满,当前线程将被挂起放到AQS队列中,等待队列非满时插入(非满条件)。
       //在等待期间,锁会被释放,其他线程可以继续对队列进行操作。
        while (count == items.length)
            notFull.await();
           //如果队列可以存放元素,则调用enqueue将元素入队
        enqueue(e);
    } finally {
        //释放锁
        lock.unlock();
    }
}

队列满时,调用notFull.await() 阻塞生产者线程。
put方法内部调用了enqueue方法实现元素入队
enqueue源码:

private void enqueue(E x) {
   //获取队列底层的数组
    final Object[] items = this.items;
    //将putindex位置的值设置为我们传入的x
    items[putIndex] = x;
    //更新putindex,如果putindex等于数组长度,则更新为0
    if (++putIndex == items.length)
        putIndex = 0;
    //队列长度+1
    count++;
    //通知队列非空,那些因为获取元素而阻塞的线程可以继续工作了
    notEmpty.signal();
}

队列非空,调用notEmpty.signal()唤醒消费者线程

take操作源码

public E take() throws InterruptedException {
       //获取锁
     final ReentrantLock lock = this.lock;
     lock.lockInterruptibly();
     try {
             //如果队列中元素个数为0,则将当前线程打断并存入AQS队列中,等待队列非空时获取并移除元素(非空条件)
         while (count == 0)
             notEmpty.await();
            //如果队列不为空则调用dequeue获取元素
         return dequeue();
     } finally {
          //释放锁
         lock.unlock();
     }
}

notEmpty和notFull如何控制ArrayBlockingQueue存取
在这里插入图片描述
ArrayBlockingQueue问题汇总

  1. ArrayBlockingQueue 是什么?它的特点是什么?
    ArrayBlockingQueue是BlockingQueue接口的有界队列实现类,常用于多线程之间的数据共享,底层采用数组实现。容量有限,一旦创建,容量不能改变
    为了保证线程安全,ArrayBlockingQueue的并发控制采用ReentrantLock,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。并且,它还支持公平和非公平两种方式的锁访问机制,默认是非公平锁。
    ArrayBlockingQueue 虽名为阻塞队列,但也支持非阻塞获取和新增元素(例如 poll() 和 offer(E e) 方法),只是队列满时添加元素会抛出异常,队列为空时获取的元素为 null,一般不会使用。

  2. ArrayBlockingQueue 和 LinkedBlockingQueue 有什么区别?


 - 底层实现:ArrayBlockingQueue 基于数组实现,LinkedBlockingQueue基于链表实现
 - 是否有界:ArrayBlockingQueue是有界队列,LinkedBlockingQueue,创建时可以不指定容量大小。
 - 锁是否分离:ArrayBlockingQueue中的锁是没有分离的,即生产和消费用的是同一个锁;LinkedBlockingQueue中的锁是分离的,即生产用的是putLock,消费是takeLock,这样可以防止生产者和消费者线程之间的锁争夺。
 - 内存占用:ArrayBlockingQueue 需要提前分配数组内存,而 LinkedBlockingQueue 则是动态分配链表节点内存。这意味着,ArrayBlockingQueue 在创建时就会占用一定的内存空间,且往往申请的内存比实际所用的内存更大,而LinkedBlockingQueue 则是根据元素的增加而逐渐占用内存空间
  1. ArrayBlockingQueue 和 ConcurrentLinkedQueue 有什么区别?
底层实现:ArrayBlockingQueue 基于数组实现,而 ConcurrentLinkedQueue 基于链表实现。
是否有界:ArrayBlockingQueue 是有界队列,必须在创建时指定容量大小,而 ConcurrentLinkedQueue 是无界队列,  可以动态地增加容量。
是否阻塞:ArrayBlockingQueue 支持阻塞和非阻塞两种获取和新增元素的方式(一般只会使用前者), ConcurrentLinkedQueue 是无界的,仅支持非阻塞式获取和新增元素。

4.ArrayBlockingQueue 的实现原理是什么?

— ArrayBlockingQueue内部维护一个定长的数组用于存储元素
 - 通过使用ReentrantLock锁对象对读写操作进行同步,即通过锁机制来实现线程安全。
 - 通过Condition实现线程间的等待和唤醒操作。

线程间的等待和唤醒具体的实现:

  • 当队列已满时,生产者线程会调用 notFull.await() 方法让生产者进行等待,等待队列非满时插入(非满条件)。
  • 当队列为空时,消费者线程会调用 notEmpty.await()方法让消费者进行等待,等待队列非空时消费(非空条件)
  • 当有新的元素被添加时,生产者线程会调用 notEmpty.signal()方法唤醒正在等待消费的消费者线程。
  • 当队列中有元素被取出时,消费者线程会调用 notFull.signal()方法唤醒正在等待插入元素的生产者线程
  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值