十七. ArrayBlockingQueue源码简析

ArrayBlockingQueue是一个基于数组的阻塞队列,它使用ReentrantLock和两个Condition(notFull和notEmpty)来实现线程安全的入队和出队操作。当队列满时,put()方法会阻塞在notFull上,而队列空时,take()方法会阻塞在notEmpty上。元素出队或入队时,相应Condition的signal()方法唤醒等待的线程,保证了队列操作的同步。
摘要由CSDN通过智能技术生成

前言

ArrayBlockingQueue是基于数组实现的阻塞队列,本文将对ArrayBlockingQueueput()take()方法的阻塞逻辑进行分析。

正文

ArrayBlockingQueue的类图如下所示。

在这里插入图片描述

ArrayBlockingQueue中持有一把重入锁lock,和一对ConditionnotEmptynotFull,这些都是在ArrayBlockingQueue的构造方法中创建出来的,如下所示。

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();
}

下面先分析ArrayBlockingQueueput()方法,如下所示。

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 加锁并响应中断
    lock.lockInterruptibly();
    try {
        // 如果当前阻塞队列已满,则在notFull上进行等待
        while (count == items.length)
            notFull.await();
        // 如果当前阻塞队列未满,则将本次添加的元素入队列
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

ArrayBlockingQueueput()方法会先加锁,然后判断当前阻塞队列是否已满,如果已满则在notFull上进行等待(等待当前阻塞队列未满),否则调用enqueue()方法将本次添加的元素入队列。分析enqueue()方法前,先再看一下ArrayBlockingQueuetake()方法的实现,如下所示。

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 加锁并响应中断
    lock.lockInterruptibly();
    try {
        // 如果当前阻塞队列为空,则在notEmpty上进行等待
        while (count == 0)
            notEmpty.await();
        // 如果当前阻塞队列不为空,则将队列头元素出队列并返回
        return dequeue();
    } finally {
        lock.unlock();
    }
}

ArrayBlockingQueuetake()方法也会先加锁,然后判断当前阻塞队列是否为空,为空则在notEmpty上进行等待(等待当前阻塞队列不为空),否则调用dequeue()方法将队列头元素出队列并返回。

现在已知,当ArrayBlockingQueue已满时,调用put()方法的线程会在notFull上进行等待,当ArrayBlockingQueue为空时,调用take()方法的线程会在notEmpty上进行等待。那么现在假如ArrayBlockingQueue已满,线程中调用了put()方法,此时线程阻塞在notFull上,相应的唤醒等待的操作就一定会出现在有元素出队列的时候,在take()方法的最后调用了dequeue()方法来将队列头元素出队列,下面看一下其实现。

private E dequeue() {
    final Object[] items = this.items;
    // 将items数组中takeIndex位置的元素获取出来
    // takeIndex位置的元素就是队列头元素
    E x = (E) items[takeIndex];
    // 置items数组中takeIndex位置为空
    items[takeIndex] = null;
    if (++takeIndex == items.length)
        // 本次队列头元素在items数组最后一个位置
        // 则下一次队列头元素在items数组第一个位置
        takeIndex = 0;
    // 本次队列头元素出队列后,阻塞队列元素个数减1
    count--;
    // 因为发生了队列元素出队列,所以迭代器也要更新
    if (itrs != null)
        itrs.elementDequeued();
    // 因为发生了队列元素出队列,此时阻塞队列一定不满
    // 那么将在notFull上第一个等待的线程唤醒
    notFull.signal();
    return x;
}

首先dequeue()方法中有一个叫做takeIndex的整型变量,该变量默认情况下初始值为0,作为元素数组items的下标索引使用,每次队列元素出队列时,均会让items数组中takeIndex索引位置的元素出队列,所以可以理解为takeIndex永远指向队列头。其次调用dequeue()的线程一定会持有锁,并且队列头元素出队列后阻塞队列也一定不满,所以dequeue()方法最后还会调用notFullsignal()方法,唤醒第一个因为调用put()方法时队列已满而在notFull上等待的线程。

现在假如ArrayBlockingQueue为空,线程中调用了take()方法,此时线程阻塞在notEmpty上,相应的唤醒等待的操作就一定会出现在有元素入队列的时候,在put()方法的最后调用了enqueue()方法来将元素入队列,下面看一下其实现。

private void enqueue(E x) {
    final Object[] items = this.items;
    // 本次入队列的元素添加到items数组的putIndex位置
    items[putIndex] = x;
    if (++putIndex == items.length)
        // 本次入队列的元素添加到了items数组最后一个位置
        // 则下一次入队列的元素需要添加到items数组第一个位置
        putIndex = 0;
    // 本次元素入队列后,阻塞队列元素个数加1
    count++;
    // 因为发生了元素入队列,此时阻塞队列一定不为空
    // 那么将在notEmpty上第一个等待的线程唤醒
    notEmpty.signal();
}

dequeue()方法相似,enqueue()方法中使用了一个叫做putIndex的整型变量,该变量默认情况下初始值为0,作为元素数组items的下标索引使用,每次元素入队列时,入队列的元素会被添加到items数组的putIndex位置,所以可以理解为putIndex永远指向队列尾。此外,调用enqueue()方法的线程也一定持有锁,并且元素入队列后阻塞队列一定不为空,所以enqueue()方法最后还会调用notEmptysignal()方法,唤醒第一个因为调用take()方法时队列为空而在notEmpty上等待的线程。

总结

  1. ArrayBlockingQueue在创建时需要指定初始容量;
  2. ArrayBlockingQueue持有一把重入锁,ArrayBlockingQueue中的元素入队列的操作add()offer()put(),以及ArrayBlockingQueue中的元素出队列的操作remove()poll()take(),全都会进行加锁;
  3. ArrayBlockingQueue持有一对Condition,分别为notFullnotEmpty,当ArrayBlockingQueue已满且有线程调用put()方法,此时调用put()方法的线程会在notFull上等待,当ArrayBlockingQueue为空且有线程调用take()方法,此时调用take()方法的线程会在notEmpty上等待;
  4. 当有元素出队列后,会唤醒第一个在notFull上等待的线程,当有元素入队列后,会唤醒第一个在notEmpty上等待的线程;
  5. ArrayBlockingQueue使用一个Object数组items来存放元素,同时还有两个整型变量分别叫做takeIndexputIndex,默认情况下初始值均为0,takeIndex标识队列头元素在items数组中的位置,putIndex标识新入队列的元素应该被添加到items数组中的哪个位置,即takeIndex一直指向队列头,putIndex一直指向队列尾。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

樱花祭的约定

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值