Java source code analysis and interview questions-ArrayBlockingQueue source code analysis

This series of related blog, Mu class reference column Java source code and system manufacturers interviewer succinctly Zhenti
below this column is GitHub address:
Source resolved: https://github.com/luanqiu/java8
article Demo: HTTPS: // GitHub. com / luanqiu / java8_demo
classmates can look at it if necessary)

Java source code analysis and interview questions-ArrayBlockingQueue source code analysis

Introductory phrase
In this section we introduce the chapter concludes with a queue: ArrayBlockingQueue. According to the literal translation, Chinese is called an array blocking queue. From the name, we know that this blocking queue uses an array at the bottom. When it comes to arrays, you may think of ArrayList and HashMap. In the new scenario, ArrayList finds the new array subscript position through size ++. HashMap calculates the subscript position through the hash algorithm, so is ArrayBlockingQueue the same? What kind of method? No, ArrayBlockingQueue uses a very wonderful way, we will wait and see.

For the convenience of explanation, the head of the team is the head of the array, and the tail of the team is the tail of the array.

1 Overall architecture

We can get some useful information from class comments:

1.1 Class notes

  1. Bounded blocking array, once the capacity is created, the subsequent size cannot be modified;
  2. The elements are ordered, sorted according to first-in first-out, insert data data from the end of the team, and take data from the head of the team;
  3. When the queue is full, the put data in the queue will be blocked, and when the queue is empty, the data in the queue will also be blocked.

It can be seen from the class notes that ArrayBlockingQueue is not the same as the general array structure class, and it cannot be dynamically expanded. If the queue is full or empty, take and put will be blocked.

1.2 Data structure

// 队列存放在 object 的数组里面
// 数组大小必须在初始化的时候手动设置,没有默认大小
final Object[] items;
 
// 下次拿数据的时候的索引位置
int takeIndex;
 
// 下次放数据的索引位置
int putIndex;
 
// 当前已有元素的大小
int count;
 
// 可重入的锁
final ReentrantLock lock;
 
// take的队列
private final Condition notEmpty;
 
// put的队列
private final Condition notFull;

The above code has two key fields, takeIndex and putIndex, which respectively indicate the index position of next time to take data and put data. So when adding data and taking data, you don't need to calculate, you can know where to add and where to get the data.

2 Initialization

During initialization, there are two important parameters: the size of the array and whether it is fair. The source code is as follows:

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    // 队列不为空 Condition,在 put 成功时使用
    notEmpty = lock.newCondition();
    // 队列不满 Condition,在 take 成功时使用
    notFull =  lock.newCondition();
}

From the source code, we can see whether the second parameter is fair, mainly used to read and write locks are fair. Random during lock contention.

For lock fairness and unfairness, we give an example: for example, the queue is now full, and there are many threads performing put operations, there must be many threads blocking waiting, when other threads execute take, it will wake up the waiting thread, If it is a fair lock, it will wake up the blocked threads in the order of blocking waiting. If it is an unfair lock, it will randomly wake up the sleeping threads.

Therefore, when the queue is full and many threads perform the put operation, if it is a fair lock, the order in which the array elements are added is the order in which the blocking threads are released. There is an order, not a fair lock, because the order in which the blocking threads are released is random , So the order in which elements are inserted into the array will not follow the order in which they were inserted.

The same is true when the queue is empty.

ArrayBlockingQueue easily realizes the insertion order of array elements through the fairness and unfairness of locks. If you want to achieve this function, what would you do? Will you think of using the lock function? In fact, we have mentioned this idea many times in the article. When we need to accomplish one thing, first look at whether the existing API can be satisfied. If possible, it can be achieved through inheritance and composition ArrayBlockingQueue 就是组合了锁的功能.

During initialization, if the original data is given, it must be noted that the size of the original data must be smaller than the capacity of the queue, otherwise an exception will be thrown, as shown in the following figure:
Insert picture description here
we wrote a demo, and the error is as follows:
Insert picture description here

3 New data

New data will be added according to the position of putIndex. The source code is as follows:

// 新增,如果队列满,无限阻塞
public void put(E e) throws InterruptedException {
    // 元素不能为空
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 队列如果是满的,就无限等待
        // 一直等待队列中有数据被拿走时,自己被唤醒
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
 
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1; 同一时刻只能一个线程进行操作此方法
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    // putIndex 为本次插入的位置
    items[putIndex] = x;
    // ++ putIndex 计算下次插入的位置
    // 如果下次插入的位置,正好等于队尾,下次插入就从 0 开始
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    // 唤醒因为队列空导致的等待线程
    notEmpty.signal();
}

From the source code, we can see that there are actually two new cases:

  1. The newly added position is centered and added directly. The figure below demonstrates that putIndex is at the position of the array index of 5, and it is not yet at the end of the queue. Then you can add it directly. Calculate the next added position should be 6;
    Insert picture description here
  2. The new position is at the end of the team, so the next time you add it, you will start from the beginning. The schematic diagram is as follows: the
    Insert picture description here
    above picture shows this line of code: if (++ putIndex == items.length) putIndex = 0;

It can be seen that when adding to the end of the team, the next addition will restart from the beginning of the team.

4 Take the data

The data is taken from the head of the team. The source code is as follows:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 如果队列为空,无限等待
        // 直到队列中有数据被 put 后,自己被唤醒
        while (count == 0)
            notEmpty.await();
        // 从队列中拿数据
        return dequeue();
    } finally {
        lock.unlock();
    }
}
 
private E dequeue() {
    final Object[] items = this.items;
    // takeIndex 代表本次拿数据的位置,是上一次拿数据时计算好的
    E x = (E) items[takeIndex];
    // 帮助 gc
    items[takeIndex] = null;
    // ++ takeIndex 计算下次拿数据的位置
    // 如果正好等于队尾的话,下次就从 0 开始拿数据
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 队列实际大小减 1
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 唤醒被队列满所阻塞的线程
    notFull.signal();
    return x;
}

It can be seen from the source code that the position of taking data every time is the position of takeIndex. After finding the data to be taken this time, it will increase the takeIndex by 1 to calculate the index position when the data is taken next time. There is a special case. If the position of taking data this time is already the tail of the team, then the position of taking data next time will start from the beginning, that is, from 0.

5 Delete data

Deleting data is very interesting, let's take a look at the core source code:

// 一共有两种情况:
// 1:删除位置和 takeIndex 的关系:删除位置和 takeIndex 一样,比如 takeIndex 是 2, 而要删除的位置正好也是 2,那么就把位置 2 的数据置为 null ,并重新计算 takeIndex 为 3。
// 2:找到要删除元素的下一个,计算删除元素和 putIndex 的关系
// 如果下一个元素不是 putIndex,就把下一个元素往前移动一位
// 如果下一个元素是 putIndex,把 putIndex 的值修改成删除的位置
void removeAt(final int removeIndex) {
    final Object[] items = this.items;
    // 情况1 如果删除位置正好等于下次要拿数据的位置
    if (removeIndex == takeIndex) {
        // 下次要拿数据的位置直接置空
        items[takeIndex] = null;
        // 要拿数据的位置往后移动一位
        if (++takeIndex == items.length)
            takeIndex = 0;
        // 当前数组的大小减一
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    // 情况 2
    } else {
        final int putIndex = this.putIndex;
        for (int i = removeIndex;;) {
            // 找到要删除元素的下一个
            int next = i + 1;
            if (next == items.length)
                next = 0;
            // 下一个元素不是 putIndex
            if (next != putIndex) {
                // 下一个元素往前移动一位
                items[i] = items[next];
                i = next;
            // 下一个元素是 putIndex
            } else {
                // 删除元素
                items[i] = null;
                // 下次放元素时,应该从本次删除的元素放
                this.putIndex = i;
                break;
            }
        }
        count--;
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    notFull.signal();
}

The situation of deleting data is more complicated. There are two cases. The first case is takeIndex == removeIndex. Let's draw a schematic diagram to see the processing method: the
Insert picture description here
second case is divided into two types:

  1. If removeIndex + 1! = PutIndex, move the next element forward by one, the schematic diagram is as follows:
    Insert picture description here
  2. If removeIndex + 1 == putIndex, modify the value of putIndex to the deleted position, the schematic diagram is as follows:
    Insert picture description here

The deletion method of ArrayBlockingQueue is actually quite complicated, and needs to consider many special scenarios.

6 Summary

The bottom of ArrayBlockingQueue is a bounded array. On the whole, it is not much different from other queues. It should be noted that when takeIndex and putIndex reach the end of the queue, they will start to cycle from 0 again. This is special. In our Pay special attention when studying source code.

Published 40 original articles · won praise 1 · views 4983

Guess you like

Origin blog.csdn.net/aha_jasper/article/details/105525628