ArrayBlockingQueue of concurrent queues

Keywords: Java

In the previous article, we talked about the LinkedBlockingQueue in the concurrent queue. This time, let's look at the ArrayBlockingQueue and its name. Let's imagine the difference between LinkedList and ArrayList. We can see that the bottom layer of ArrayBlockingQueue must be implemented based on array, which is a bounded array;

The components of ArrayBlockingQueue are similar to LinkedBlockingQueue, but also have two conditional variables: maintain the blocking queue and implement the production consumer mode;

 

1, Simple understanding of ArrayBlockingQueue

Let's take a look at some common attributes:

//Array to hold queue elements
final Object[] items;
//Exit index
int takeIndex;
//Entry index
int putIndex;
//Number of elements in the queue
int count;
//Exclusive lock
final ReentrantLock lock;
//If the array is empty and the thread fetches data, it will be put into this condition variable to block
private final Condition notEmpty;
//When the queue is full, and threads add data to the array, the threads are dropped here to block
private final Condition notFull;

 

 

Since this is a bounded array, let's look at the constructor again:

//Specify capacity, default is unfair policy
public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}
//Specify capacity and exclusive lock policy
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();
}
//You can specify capacity, lock policy, and initialization data
public ArrayBlockingQueue(int capacity, boolean fair,
                            Collection<? extends E> c) {
    this(capacity, fair);

    final ReentrantLock lock = this.lock;
    lock.lock(); // Lock only for visibility, not mutual exclusion
    try {
        int i = 0;
        try {
            for (E e : c) {
                checkNotNull(e);
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
        count = i;
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        lock.unlock();
    }
}

 

 

2, offer method

Add an element to the end of the queue. If it is added successfully, it will return true. If the queue is full, it will lose the current element and return false directly. The method is not blocked;

public boolean offer(E e) {
    //Non empty test
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //If the actual quantity and the maximum capacity in the array are equal, adding fails, return false
        if (count == items.length)
            return false;
        else {
            //Successfully added. The method is implemented as follows
            enqueue(e);
            return true;
        }
    } finally {
        //Release lock
        lock.unlock();
    }
}
private void enqueue(E x) {
   //Get the array
    final Object[] items = this.items;
    //stay putIndex Put data in this location x,Then put putIndex Add one to indicate that this parameter represents the index of the next data location
    items[putIndex] = x;
    //Here putIndex For example, if the maximum capacity of an array is 5, then the maximum index value should be 4. If putIndex Equal to 5, indicating array
    //Out of bounds, reset this index to 0
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    //After adding, it indicates that there is data in the array. Here, it wakes up the thread blocked by fetching data from the array
    notEmpty.signal();
}

 

 

3, put method

Insert an element at the end of the queue. If the queue is idle, it will return true successfully. If the queue is full, it will block the current thread to the condition queue of notFull. If there is idle, it will wake up. If there is any interrupt during the blocking process, it will respond to it;

public void put(E e) throws InterruptedException {
    //Non empty check
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    //Note how the lock is acquired
    lock.lockInterruptibly();
    try {
        //If the thread is full, put the current thread in the notFull In the blocking queue of condition variable
        while (count == items.length)
            notFull.await();
        //Add data if not full
        enqueue(e);
    } finally {
        //Release lock
        lock.unlock();
    }
}

 

 

4, poll method

The header gets and removes an element. If the queue is empty, null is returned, and the method is not blocked;

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //If the queue is empty, return null
        //If the queue is not empty, call dequeue Method to get and delete the elements in the queue header
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}
private E dequeue() {
    //Get array
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    //Obtain takeIndex The element of the location, which will return
    E x = (E) items[takeIndex];
    //Then will takeInde Position set to empty
    items[takeIndex] = null;
    //If takeIndex It's the last position of the array, so takeIndex Reset to 0
    if (++takeIndex == items.length)
        takeIndex = 0;
    //Actual quantity minus one
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    //awaken notFull Middle thread
    notFull.signal();
    return x;
}

 

 

5, take method

Get and delete the element in the head of the current queue. If the queue is empty and the current thread is blocked until it is awakened, it will respond to the interrupt;

 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //Obtain locks in an interruptible way
        lock.lockInterruptibly();
        try {
            //If the array is empty, wake up notEmpty Threads in the condition queue in
            while (count == 0)
                notEmpty.await();
            //Get and delete the header node
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

 

 

6, peek method

Just get the header element and do not delete it. If the queue is empty, null will be returned. This method is thread free

public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return itemAt(takeIndex); // null when queue is empty
    } finally {
        lock.unlock();
    }
}

//Get the index in the array as takeIndex Data in
@SuppressWarnings("unchecked")
final E itemAt(int i) {
    return (E) items[i];
}

 

 

Seven. Conclusion

After understanding the LinkedBlockingQueue mentioned in the previous blog, it's actually too easy to read this one again, which is to operate the array! It is shown in the following figure:

Posted by hcspider on Sun, 09 Feb 2020 00:42:38 -0800