JDK Source Array Blocking Queue

Posted by djrichwz on Sun, 28 Jul 2019 07:52:30 +0200

Thread is a very important part of JDK. All application services are inseparable from the relevant operations of threads. Thread pools are recommended for operation and management of a large number of threads. Thread pools provided by JDK itself are often used in services. Executors are often used to create them in the past, but Ali The specification also points out the hidden dangers. From the source code point of view, a lot of queue-related classes are used internally, so before we go into the thread section, we need to understand the content of queue-related. This article begins with Array BlockingQueue to explain the commonly used queues in JDK.

Preface

First we need to understand its structural features: Array BlockingQueue is a thread-safe bounded blocking queue for array implementations

  • Internally, it is realized through arrays, which can be seen from the source code part.
  • Thread security means that Array BlockingQueue protects competing resources through ReentrantLock and achieves mutually exclusive access to competing resources by multiple threads.
  • Bounded. Array BlockingQueue corresponds to arrays that are bounded.
  • Blocking queue refers to the multi-threaded access to competitive resources, when competitive resources have been acquired by a thread, other threads to obtain the resources need to block and wait; furthermore, Array Blocking Queue ranks elements according to FIFO (first in first out) principle, elements are inserted from the tail to the queue, and return from the beginning.

From the structural features above, we can basically understand its main implementation. The following is a little more specific description of its internal implementation:

  1. Array BlockingQueue inherits from AbstractQueue and implements the BlockingQueue interface
  2. Array BlockingQueue stores data through Object [] arrays internally, which verifies the characteristics of the upper implementation through arrays. The size of the Array BlockingQueue, that is, the capacity of the array, is specified when creating the Array BlockingQueue
  3. Array BlockingQueue contains a ReentrantLock object (lock). ReentrantLock is a re-entrant mutex, and Array BlockingQueue implements "mutex access of competing resources by multi-threads" based on this mutex. Furthermore, ReentrantLock is divided into fair locks and unfair locks, which can be specified when creating an Array Blocking Queue for the specific use of fair locks or unfair locks; furthermore, Array Blocking Queue defaults to using unfair locks.
  4. Array BlockingQueue contains two Condition objects (notEmpty and notFull). Condition also depends on Array Blocking Queue, through which more accurate access to Array Blocking Queue can be achieved. For example, if thread A wants to fetch data and the array is just empty, the thread will execute notEmpty.await() to wait; when another thread (thread B) inserts data into the array, it will call notEmpty.signal() to wake up the "waiting thread on notEmpty". At this point, thread A is awakened to continue running. If thread C wants to insert data when the array is full, the thread will execute notFull.await() to wait; when another thread (thread D) takes out the data, it will call notFull.signal() to wake up the "waiting thread on notFull". At this point, thread C is awakened to continue running.

Array BlockingQueue's entry and exit operations are relatively simple and easy to understand. The complex part lies in the iterator part. Here's a step-by-step explanation through the source code.

Class Definition

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable

Constants/variables

    /** The queued items */
    // Use arrays for internal implementation of queues
    final Object[] items;

    /** items index for next take, poll, peek or remove */
    // Next index of queue elements retrieved from the queue
    int takeIndex;

    /** items index for next put, offer, or add */
    // Next insert queue element index
    int putIndex;

    /** Number of elements in the queue */
    // queue length
    int count;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /** Main lock guarding all access */
    // mutex
    final ReentrantLock lock;

    /** Condition for waiting takes */
    // Non-empty semaphore
    private final Condition notEmpty;

    /** Condition for waiting puts */
    // Non-full semaphore
    private final Condition notFull;

    /**
     * Shared state for currently active iterators, or null if there
     * are known not to be any.  Allows queue operations to update
     * iterator state.
     */
    // Iterators maintain lists, and each queue update operation requires updating the iterator to ensure correctness
    // The second part, iterator section, is explained separately. Let's have a brief understanding.
    transient Itrs itrs = null;

Construction method

By default, unfair locks are used, creating a mutex and two Condition s to help complete the operation of the entire queue. As can be seen from the construction method, queue capacity must be passed on.

    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
    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();
    }
    public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        // Use lock operations to ensure visibility, because item itself does not guarantee visibility, preventing inconsistencies in threads'memory under concurrent operations
        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;
            // The queue is full of putIndex at 0, which can also be seen here that the array is recycled, reaching its maximum, set to 0, similar to a ring array.
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

Important methods

enqueue

Queuing operations, adding elements to the queue, if and only if lock s are held. Threads that enter the waiting state through notEmpty.await() before waking up through notEmpty.signal() after each addition

    /**
     * Inserts element at current put position, advances, and signals.
     * Call only when holding lock.
     */
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        // When the maximum index of an array is reached, the putIndex points to the position of the array at 0.
        if (++putIndex == items.length)
            putIndex = 0;
        // Queue length plus 1
        count++;
        notEmpty.signal();
    }

dequeue

Out of the queue operation, get the queue elements, if and only if the lock is held. The thread that enters the waiting state through notFull.await() before waking up through notFull.signal() after each acquisition

    /**
     * Extracts element at current take position, advances, and signals.
     * Call only when holding lock.
     */
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        // Point to empty, delete this element in the queue
        items[takeIndex] = null;
        // takeIndex is the same as putIndex. When the maximum index of an array is reached, it points to the position of 0 of the array.
        if (++takeIndex == items.length)
            takeIndex = 0;
        // Queue length minus 1
        count--;
        // Update element data in iterator to ensure the correctness of iteration in concurrent operation
        // Look back at the back iterator instructions
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

add/offer/put

There are several ways to join the team, and each method is slightly different.

    public boolean add(E e) {
        // Invoking offer to get the result unsuccessfully throws an error
        return super.add(e);
    }
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
    public boolean offer(E e) {
        // Check whether the element is empty
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // When the queue is full, it returns false
            if (count == items.length)
                return false;
            else {
                // Join the team
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        // Blocking wait time nanoseconds
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        // lockInterruptibly method can be interrupted
        lock.lockInterruptibly();
        try {
            while (count == items.length) {
                if (nanos <= 0)
                    // Overtime return
                    return false;
                // When the queue is full, it blocks and waits for nanos nanoseconds
                nanos = notFull.awaitNanos(nanos);
            }
            // Join the team
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                // The queue is full of congestion waiting
                notFull.await();
            // Join the team
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

Adding elements to the queue mainly includes the three methods mentioned above. The differences are as follows:

Method name (with parameters) Explain
add(E e) Actual call offer, element queue operation, success returns true, failure throws IllegalStateException
offer(E e) Element queuing operation, successful return true, failure return false
put(E e) Interruptible, element queuing operation, queue full, blocking wait until notified to be queued
offer(E e, long timeout, TimeUnit unit) Interruptible, element queuing operation, setting blocking waiting timeout time, returning false when timeout, and true when successful queuing

The above operations are actually defined in the BlockingQueue interface and need to be implemented by itself according to the interface.

poll/take/peek/remove

There are also slightly different ways to get out of the team as well as to get into the team, as follows

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // The queue returns null without elements and queue operations with elements
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        // Interruptible
        lock.lockInterruptibly();
        try {
            while (count == 0)
                // When queues are empty, they block and wait until they are awakened.
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                // Blocking wait timeout nanos nanoseconds
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // Returns the value at the takeIndex index of the queue. Without queue operation, the element will not be deleted.
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            lock.unlock();
        }
    }
    
    public boolean remove(Object o) {
        if (o == null) return false;
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count > 0) {
                final int putIndex = this.putIndex;
                int i = takeIndex;
                do {
                    if (o.equals(items[i])) {
                        // Remove the elements at i, while the queue is sorted, and the elements after removing move forward in turn to fill the gaps.
                        removeAt(i);
                        return true;
                    }
                    // Loop to array length, continue from 0
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

Getting elements from the queue mainly includes the four methods mentioned above. Except peek does not perform the queue operation, remove is deleted, and the rest of them need to perform the queue operation. The differences are as follows:

Method name (with parameters) Explain
poll() Out of the queue operation, the queue returns null when empty, and the corresponding element if not empty
take() Interruptible, out-of-queue operation, queue empty, blocking wait until notified to get the value back
poll(long timeout, TimeUnit unit) Interruptible, out-of-line operation, setting blocking waiting timeout time, returning null when timeout, and returning corresponding elements when successful
peek() Returns only the value of the queue element, but does not perform the queue operation
remove(Object o) Delete the corresponding elements in the queue (which can be anywhere in the queue), and the queue needs to be sorted out

removeAt

Remove the queue element at the specified location and adjust the queue

    /**
     * Deletes item at array index removeIndex.
     * Utility for remove(Object) and iterator.remove.
     * Call only when holding lock.
     */
    void removeAt(final int removeIndex) {
        // assert lock.getHoldCount() == 1;
        // assert items[removeIndex] != null;
        // assert removeIndex >= 0 && removeIndex < items.length;
        final Object[] items = this.items;
        // When removing the index of the queue element is the index of the queue, you can refer to the out-of-queue operation.
        if (removeIndex == takeIndex) {
            // removing front item; just advance
            // Remove Queue Element Empty
            items[takeIndex] = null;
            // Adjust Queue Listing Queue Index
            if (++takeIndex == items.length)
                takeIndex = 0;
            // Queue length minus 1
            count--;
            // If there is an iterator, it needs to be updated, which is later said
            if (itrs != null)
                itrs.elementDequeued();
        } else {
            // This indicates that the deleted element is not a queue index, and the deleted element is in the middle of the queue.
            // an "interior" remove

            // slide over all others up through putIndex.
            final int putIndex = this.putIndex;
            // Adjust the queue element and move all elements forward one bit after the queue deletes the element
            for (int i = removeIndex;;) {
                int next = i + 1;
                if (next == items.length)
                    next = 0;
                if (next != putIndex) {
                    items[i] = items[next];
                    i = next;
                } else {
                    // next = putIndex indicates that it is the previous element of putIndex, and then the update of putIndex ends by blanking it.
                    items[i] = null;
                    this.putIndex = i;
                    break;
                }
            }
            count--;
            // Synchronized iterator operation, described later
            if (itrs != null)
                itrs.removedAt(removeIndex);
        }
        // Wake up queuing threads
        notFull.signal();
    }

The source code assigns the class member variables to the local variables in the method many times, such as ReentrantLock lock = this.lock. In fact, in order to speed up the running efficiency of the program, it directly refers to the class members every time, such as this.lock, the reading operation is relative to the direct operation of the method stack inside the method. Partial variables are relatively inefficient, involving the JVM part, not detailed, interested in self-search for relevant information.

Iterator Description

The above source code part is the queue operation part, which is well understood as a whole. The most complex iterator part is described below.

    public Iterator<E> iterator() {
        return new Itr();
    }

For external callers, we need to get the corresponding iterator first. The source code is as above. One thing we can see from this place is that the iterator is a snapshot-like save of the queue at the time when the method is called. It saves the queue state at that time. We can also see the save in the internal class source code below. What attributes are not saved in the queue, so it will cause a big problem. How to ensure the correctness of iterator values in multi-threaded frequent queue entry and exit operations? Of course, if we keep a copy of the queue element, it is not correct in itself. This is where the complexity of iterators lies. Ensuring the correctness of iterators leads to the complexity of iterator source code.

Here we first draw a conclusion:

  1. The iterator Itr created saves the status of the queue at that time (no queue element values are saved)
  2. Created iterators are put into Itrs (Iterator Maintenance List) for unified maintenance and management
  3. Each next() operation will update the iterator status to see if it is exhausted or invalid, and clean up the invalid iterator through doSomeSweeping of Itrs
  4. The iterator maintenance list Itrs is updated by itrs.elementDequeued() during the queue operation
  5. The value of the next call to the next method is saved when the iterator is first created, so it is true when hasNext is called, the queue is empty before next() is called, and the next() is still returned, which is a kind of protection.
  6. When an iterator is created, the elements in the queue are replaced by two loops, then the iterator is invalid.

Iterator-related internal classes

Internal classes are mainly used in iteration. As can be seen from iterator method, an Itr object is created again every time it is invoked. How to ensure the correctness of iterator when adding, deleting and modifying? Look at internal implementation

    private class Itr implements Iterator<E> {
        /** Index to look for new nextItem; NONE at end */
        // The cursor pointing to the next iteration element ends with NONE (-1)
        private int cursor;

        /** Element to be returned by next call to next(); null if none */
        // Next time you call the element returned by next(), null will be returned, which is 5 on the corresponding conclusion.
        private E nextItem;

        /** Index of nextItem; NONE if none, REMOVED if removed elsewhere */
        // The index value of the next element, nextItem, returns NONE (-1) if empty, and REMOVED (-2) if removed.
        private int nextIndex;

        /** Last element returned; null if none or not detached. */
        // The element returned by the last call to next() returns null
        private E lastItem;

        /** Index of lastItem, NONE if none, REMOVED if removed elsewhere */
        // The index value of the previous element returns NONE if empty and REMOVED if removed
        private int lastRet;

        /** Previous value of takeIndex, or DETACHED when detached */
        // The value corresponding to the last takeIndex is DETACHED (-3) when it is in an invalid state, identifying the iterator DETACHED state with this variable
        private int prevTakeIndex;

        /** Previous value of iters.cycles */
        // The value of the previous cycles
        private int prevCycles;

        /** Special index value indicating "not available" or "undefined" */
        // Identify unavailable or undefined values
        private static final int NONE = -1;

        /**
         * Special index value indicating "removed elsewhere", that is,
         * removed by some operation other than a call to this.remove().
         */
        // Identification element removed
        private static final int REMOVED = -2;

        /** Special value for prevTakeIndex indicating "detached mode" */
        // Identify prevTakeIndex as invalid
        private static final int DETACHED = -3;

        Itr() {
            // assert lock.getHoldCount() == 0;
            // When constructed, the last element index is empty
            lastRet = NONE;
            // Use mutexes
            final ReentrantLock lock = ArrayBlockingQueue.this.lock;
            lock.lock();
            try {
                // Queue empty, initialize variables
                if (count == 0) {
                    // assert itrs == null;
                    cursor = NONE;
                    nextIndex = NONE;
                    // Iterator of Invalid State
                    prevTakeIndex = DETACHED;
                } else {
                    // At this point the queue is not empty
                    // takeIndex Recording Team Out
                    final int takeIndex = ArrayBlockingQueue.this.takeIndex;
                    prevTakeIndex = takeIndex;
                    // Next time you use the value returned by the iteration, the return value of the call next is saved here.
                    nextItem = itemAt(nextIndex = takeIndex);
                    // The cursor points to takeIndex+1
                    cursor = incCursor(takeIndex);
                    // Use Itrs to maintain all iterators
                    if (itrs == null) {
                        // Null rule creation
                        itrs = new Itrs(this);
                    } else {
                        // Non-empty sub-iterator registration
                        itrs.register(this); // in this order
                        // Clean up the iterator, and each time you create a new iterator, you do a simple clean-up operation
                        itrs.doSomeSweeping(false);
                    }
                    // Save the number of queue loops, cycles explained in itrs
                    prevCycles = itrs.cycles;
                    // assert takeIndex >= 0;
                    // assert prevTakeIndex == takeIndex;
                    // assert nextIndex >= 0;
                    // assert nextItem != null;
                }
            } finally {
                lock.unlock();
            }
        }

        // To determine whether the iterator is invalid, use prevTakeIndex to determine
        boolean isDetached() {
            // assert lock.getHoldCount() == 1;
            return prevTakeIndex < 0;
        }

        // Cursor value + 1
        private int incCursor(int index) {
            // assert lock.getHoldCount() == 1;
            // Begin at 0 to maximize
            if (++index == items.length)
                index = 0;
            // The same position as the next entry element indicates that the queue has no elements, and NONE is placed.
            if (index == putIndex)
                index = NONE;
            return index;
        }

        /**
         * Returns true if index is invalidated by the given number of
         * dequeues, starting from prevTakeIndex.
         */
        // Starting with prevTakeIndex, if the queue index index is invalid, it returns true
        // In incorporate Dequeues, compare the distance between index, prevTakeIndex and actual distance
        private boolean invalidated(int index, int prevTakeIndex,
                                    long dequeues, int length) {
            // Set the initialization time to less than 0
            if (index < 0)
                return false;
            // Distance between current index and prevTakeIndex
            int distance = index - prevTakeIndex;
            // Cyclic operation, well understood, plus array length, is the correct distance
            if (distance < 0)
                distance += length;
            // If distance is less than the actual distance, invalid return true  
            return dequeues > distance;
        }

        /**
         * Adjusts indices to incorporate all dequeues since the last
         * operation on this iterator.  Call only from iterating thread.
         */
        // After iteration, the element queues to adjust the index
        private void incorporateDequeues() {
            // assert lock.getHoldCount() == 1;
            // assert itrs != null;
            // assert !isDetached();
            // assert count > 0;

            // Get the current variable
            final int cycles = itrs.cycles;
            final int takeIndex = ArrayBlockingQueue.this.takeIndex;
            final int prevCycles = this.prevCycles;
            final int prevTakeIndex = this.prevTakeIndex;

            // Cycles!= prevCycles indicates that the queue has been recycled, which is equivalent to rejoining and leaving the queue from zero.
            // TakeIndex!= prevTakeIndex indicates that the queue elements are queued and need to be reordered
            if (cycles != prevCycles || takeIndex != prevTakeIndex) {
                final int len = items.length;
                // how far takeIndex has advanced since the previous
                // operation of this iterator
                // Length of actual queue movement
                long dequeues = (cycles - prevCycles) * len
                    + (takeIndex - prevTakeIndex);

                // Check indices for invalidation
                // The element at lastRet has been removed
                if (invalidated(lastRet, prevTakeIndex, dequeues, len))
                    lastRet = REMOVED;
                // The element at nextIndex has been removed
                if (invalidated(nextIndex, prevTakeIndex, dequeues, len))
                    nextIndex = REMOVED;
                // Invalid cursor index set to current queue takeIndex
                if (invalidated(cursor, prevTakeIndex, dequeues, len))
                    cursor = takeIndex;
                
                // Iterator invalid cleaning operation
                if (cursor < 0 && nextIndex < 0 && lastRet < 0)
                    detach();
                else {
                    // Update index
                    this.prevCycles = cycles;
                    this.prevTakeIndex = takeIndex;
                }
            }
        }

        /**
         * Called when itrs should stop tracking this iterator, either
         * because there are no more indices to update (cursor < 0 &&
         * nextIndex < 0 && lastRet < 0) or as a special exception, when
         * lastRet >= 0, because hasNext() is about to return false for the
         * first time.  Call only from iterating thread.
         */
        // 
        private void detach() {
            // Switch to detached mode
            // assert lock.getHoldCount() == 1;
            // assert cursor == NONE;
            // assert nextIndex < 0;
            // assert lastRet < 0 || nextItem == null;
            // assert lastRet < 0 ^ lastItem != null;
            if (prevTakeIndex >= 0) {
                // assert itrs != null;
                // Set iterator DETACHED state, invalid state
                prevTakeIndex = DETACHED;
                // try to unlink from itrs (but not too hard)
                // Clean up all iterators once
                itrs.doSomeSweeping(true);
            }
        }

        /**
         * For performance reasons, we would like not to acquire a lock in
         * hasNext in the common case.  To allow for this, we only access
         * fields (i.e. nextItem) that are not modified by update operations
         * triggered by queue modifications.
         */
        // For performance reasons, locks are not allowed here
        public boolean hasNext() {
            // assert lock.getHoldCount() == 0;
            // Judge nextItem directly, so the next empty queue at the first initialization is not empty here, it will return the first queue value.
            if (nextItem != null)
                return true;
            // nextItem will not enter noNext until it is empty
            noNext();
            return false;
        }
        // No element, clean up
        private void noNext() {
            final ReentrantLock lock = ArrayBlockingQueue.this.lock;
            lock.lock();
            try {
                // assert cursor == NONE;
                // assert nextIndex == NONE;
                // PrevTakeIndex >= 0, need to be processed
                if (!isDetached()) {
                    // assert lastRet >= 0;
                    incorporateDequeues(); // might update lastRet
                    if (lastRet >= 0) {
                        // Save the lastItem value, which is needed in the remove method
                        lastItem = itemAt(lastRet);
                        // assert lastItem != null;
                        detach();
                    }
                }
                // assert isDetached();
                // assert lastRet < 0 ^ lastItem != null;
            } finally {
                lock.unlock();
            }
        }

        public E next() {
            // assert lock.getHoldCount() == 0;
            // Iterative return value, the nextItem value is determined before each call to next
            final E x = nextItem;
            if (x == null)
                throw new NoSuchElementException();
            final ReentrantLock lock = ArrayBlockingQueue.this.lock;
            lock.lock();
            try {
                if (!isDetached())
                    incorporateDequeues();
                // assert nextIndex != NONE;
                // assert lastItem == null;
                // Use when remove is called after next call to delete elements
                lastRet = nextIndex;
                final int cursor = this.cursor;
                if (cursor >= 0) {
                    // Return value of next iteration
                    nextItem = itemAt(nextIndex = cursor);
                    // assert nextItem != null;
                    // Cursor plus 1
                    this.cursor = incCursor(cursor);
                } else {
                    // No next iteration element
                    nextIndex = NONE;
                    nextItem = null;
                }
            } finally {
                lock.unlock();
            }
            return x;
        }

        public void remove() {
            // assert lock.getHoldCount() == 0;
            final ReentrantLock lock = ArrayBlockingQueue.this.lock;
            lock.lock();
            try {
                if (!isDetached())
                    incorporateDequeues(); // might update lastRet or detach
                final int lastRet = this.lastRet;
                this.lastRet = NONE;
                // Last Ret is needed for deletion
                if (lastRet >= 0) {
                    if (!isDetached())
                        removeAt(lastRet);
                    else {
                        // Handles the special case of calling iterator. remove () after hasNext() returns false
                        final E lastItem = this.lastItem;
                        // assert lastItem != null;
                        this.lastItem = null;
                        // Delete as expected
                        if (itemAt(lastRet) == lastItem)
                            removeAt(lastRet);
                    }
                } else if (lastRet == NONE)
                    // Error throwing when lastRet is NONE
                    throw new IllegalStateException();
                // else lastRet == REMOVED and the last returned element was
                // previously asynchronously removed via an operation other
                // than this.remove(), so nothing to do.

                if (cursor < 0 && nextIndex < 0)
                    detach();
            } finally {
                lock.unlock();
                // assert lastRet == NONE;
                // assert lastItem == null;
            }
        }

        /**
         * Called to notify the iterator that the queue is empty, or that it
         * has fallen hopelessly behind, so that it should abandon any
         * further iteration, except possibly to return one more element
         * from next(), as promised by returning true from hasNext().
         */
        // When queues are empty or invalid, they should be cleaned up and internal variable values updated
        void shutdown() {
            // assert lock.getHoldCount() == 1;
            cursor = NONE;
            if (nextIndex >= 0)
                nextIndex = REMOVED;
            if (lastRet >= 0) {
                lastRet = REMOVED;
                lastItem = null;
            }
            prevTakeIndex = DETACHED;
            // nextItem is not empty because next methods that may be called need to be used
            // Don't set nextItem to null because we must continue to be
            // able to return it on next().
            //
            // Caller will unlink from itrs when convenient.
        }
        
        // Calculate the distance between index and prevTakeIndex
        private int distance(int index, int prevTakeIndex, int length) {
            int distance = index - prevTakeIndex;
            if (distance < 0)
                distance += length;
            return distance;
        }

        /**
         * Called whenever an interior remove (not at takeIndex) occurred.
         *
         * @return true if this iterator should be unlinked from itrs
         */
        // Called when the internal elements of the queue are deleted to ensure the correctness of the iterator
        boolean removedAt(int removedIndex) {
            // assert lock.getHoldCount() == 1;
            // Returns true directly when the current iterator is invalid
            if (isDetached())
                return true;
            
            final int cycles = itrs.cycles;
            final int takeIndex = ArrayBlockingQueue.this.takeIndex;
            final int prevCycles = this.prevCycles;
            final int prevTakeIndex = this.prevTakeIndex;
            final int len = items.length;
            int cycleDiff = cycles - prevCycles;
            // Deleted index location is less than takeIndex, number of iterations + 1
            if (removedIndex < takeIndex)
                cycleDiff++;
            // Real distance to delete elements
            final int removedDistance =
                (cycleDiff * len) + (removedIndex - prevTakeIndex);
            // assert removedDistance >= 0;
            int cursor = this.cursor;
            if (cursor >= 0) {
                int x = distance(cursor, prevTakeIndex, len);
                // The cursor points to the deleted element position
                if (x == removedDistance) {
                    // There are no more elements. Let's set NONE.
                    if (cursor == putIndex)
                        this.cursor = cursor = NONE;
                }
                // The cursor has exceeded removedDistance, and the index cursor needs - 1, because deleted elements are counted in the cursor.
                // One minus one is needed to ensure the correctness of the iterator here after the overall element position is adjusted after the queue deletes the element.
                else if (x > removedDistance) {
                    // assert cursor != prevTakeIndex;
                    this.cursor = cursor = dec(cursor);
                }
            }
            int lastRet = this.lastRet;
            // Last Ret Ibid.
            if (lastRet >= 0) {
                int x = distance(lastRet, prevTakeIndex, len);
                if (x == removedDistance)
                    this.lastRet = lastRet = REMOVED;
                else if (x > removedDistance)
                    this.lastRet = lastRet = dec(lastRet);
            }
            // nextIndex Ibid.
            int nextIndex = this.nextIndex;
            if (nextIndex >= 0) {
                int x = distance(nextIndex, prevTakeIndex, len);
                if (x == removedDistance)
                    this.nextIndex = nextIndex = REMOVED;
                else if (x > removedDistance)
                    this.nextIndex = nextIndex = dec(nextIndex);
            }
            // Iterator invalid state
            else if (cursor < 0 && nextIndex < 0 && lastRet < 0) {
                this.prevTakeIndex = DETACHED;
                return true;
            }
            return false;
        }

        /**
         * Called whenever takeIndex wraps around to zero.
         *
         * @return true if this iterator should be unlinked from itrs
         */
        //  Called whenever takeIndex loops to 0
        boolean takeIndexWrapped() {
            // assert lock.getHoldCount() == 1;
            if (isDetached())
                return true;
            if (itrs.cycles - prevCycles > 1) {
                // All the elements that existed at the time of the last
                // operation are gone, so abandon further iteration.
                // All elements of iterator are no longer present in the queue, and the iterator is invalid. It can also be seen here that the difference of 2 is invalid.
                shutdown();
                return true;
            }
            return false;
        }

    }

To mention, Node in the following Itrs uses weak references, which can be timely reclaimed by GC when the iterator instance is used up (for example, null), as well as JVM. Find the relevant information by yourself.

    class Itrs {

        /**
         * Node in a linked list of weak iterator references.
         */
        // Inheriting WeakReference to implement Node's weak reference, you can also see here that it's a linked list.
        private class Node extends WeakReference<Itr> {
            Node next;

            Node(Itr iterator, Node next) {
                super(iterator);
                this.next = next;
            }
        }

        /** Incremented whenever takeIndex wraps around to 0 */
        // takeIndex loop to 0 plus 1, record the number of cycles
        int cycles = 0;

        /** Linked list of weak iterator references */
        // Header node
        private Node head;

        /** Used to expunge stale iterators */
        // Record the node cleaned up last time for easy use next time
        private Node sweeper = null;
        
        // There are two modes for each iterator cleanup operation, one with fewer lookups and the other with more lookups.
        private static final int SHORT_SWEEP_PROBES = 4;
        private static final int LONG_SWEEP_PROBES = 16;

        Itrs(Itr initial) {
            register(initial);
        }

        /**
         * Sweeps itrs, looking for and expunging stale iterators.
         * If at least one was found, tries harder to find more.
         * Called only from iterating thread.
         *
         * @param tryHarder whether to start in try-harder mode, because
         * there is known to be at least one iterator to collect
         */
        // Cleaning operations, using tryHarder to determine which cleaning mode to use
        void doSomeSweeping(boolean tryHarder) {
            // assert lock.getHoldCount() == 1;
            // assert head != null;
            int probes = tryHarder ? LONG_SWEEP_PROBES : SHORT_SWEEP_PROBES;
            Node o, p;
            final Node sweeper = this.sweeper;
            boolean passedGo;   // to limit search to one full sweep

            if (sweeper == null) {
                // If there is no previous record, start from scratch.
                o = null;
                p = head;
                passedGo = true;
            } else {
                // The last record continues to be cleaned up
                o = sweeper;
                p = o.next;
                passedGo = false;
            }

            for (; probes > 0; probes--) {
                if (p == null) {
                    if (passedGo)
                        break;
                    o = null;
                    p = head;
                    passedGo = true;
                }
                final Itr it = p.get();
                final Node next = p.next;
                // Iterator Invalidation Processing
                if (it == null || it.isDetached()) {
                    // found a discarded/exhausted iterator
                    // If an iterator is found to be invalid, it is converted to try harder mode
                    probes = LONG_SWEEP_PROBES; // "try harder"
                    // unlink p
                    p.clear();
                    p.next = null;
                    if (o == null) {
                        head = next;
                        if (next == null) {
                            // We've run out of iterators to track; retire
                            itrs = null;
                            return;
                        }
                    }
                    else
                        o.next = next;
                } else {
                    o = p;
                }
                p = next;
            }
            // Record the node data to be used for the next cleanup
            this.sweeper = (p == null) ? null : o;
        }

        /**
         * Adds a new iterator to the linked list of tracked iterators.
         */
        // Add the iterator to the iteration management list and from the top
        void register(Itr itr) {
            // assert lock.getHoldCount() == 1;
            head = new Node(itr, head);
        }

        /**
         * Called whenever takeIndex wraps around to 0.
         *
         * Notifies all iterators, and expunges any that are now stale.
         */
        // Called when queue takeIndex loops to 0
        void takeIndexWrapped() {
            // assert lock.getHoldCount() == 1;
            // Cycles plus 1 records the number of cycles
            cycles++;
            // Call each iterator's takeIndexWrapped update while cleaning up invalid iterators
            for (Node o = null, p = head; p != null;) {
                final Itr it = p.get();
                final Node next = p.next;
                if (it == null || it.takeIndexWrapped()) {
                    // unlink p
                    // assert it == null || it.isDetached();
                    p.clear();
                    p.next = null;
                    if (o == null)
                        head = next;
                    else
                        o.next = next;
                } else {
                    o = p;
                }
                p = next;
            }
            // Iterative management list empty, no iterator
            if (head == null)   // no more iterators to track
                itrs = null;
        }

        /**
         * Called whenever an interior remove (not at takeIndex) occurred.
         *
         * Notifies all iterators, and expunges any that are now stale.
         */
        // Call removedAt of the iteration management list when the queue deletes elements
        void removedAt(int removedIndex) {
            for (Node o = null, p = head; p != null;) {
                final Itr it = p.get();
                final Node next = p.next;
                // Iteration management list itrs calls removedAt of each iterator to complete the update
                // Cleaning up invalid iterators at the same time
                if (it == null || it.removedAt(removedIndex)) {
                    // unlink p
                    // assert it == null || it.isDetached();
                    p.clear();
                    p.next = null;
                    if (o == null)
                        head = next;
                    else
                        o.next = next;
                } else {
                    o = p;
                }
                p = next;
            }
            if (head == null)   // no more iterators to track
                itrs = null;
        }

        /**
         * Called whenever the queue becomes empty.
         *
         * Notifies all active iterators that the queue is empty,
         * clears all weak refs, and unlinks the itrs datastructure.
         */
        // Queues are called in space-time, and the iteration manager cleans up all iterators
        void queueIsEmpty() {
            // assert lock.getHoldCount() == 1;
            for (Node p = head; p != null; p = p.next) {
                Itr it = p.get();
                if (it != null) {
                    p.clear();
                    it.shutdown();
                }
            }
            head = null;
            itrs = null;
        }

        /**
         * Called whenever an element has been dequeued (at takeIndex).
         */
        // Call to determine whether the queue is empty and the number of queue loops when queuing
        void elementDequeued() {
            // assert lock.getHoldCount() == 1;
            if (count == 0)
                queueIsEmpty();
            else if (takeIndex == 0)
                takeIndexWrapped();
        }
    }

The two inner classes above realize the operation of iterator, which is more complex. An intermediate class Itrs is used to manage and update all iterators. In queue operation, the relevant variables of iterator should be updated to ensure the correctness of iteration.

summary

Overall, Array BlockingQueue itself is not a very complex piece of queue operation, the basic blocking queue operation can understand already ok ay. It is better to understand the lock part simply, after all, AQS is involved. It is more complicated in the iterator part, but ultimately it is to ensure the correctness of the data in the iteration. This aspect is better understood.

In the above source code analysis, we can also see some problems. Most of the operation methods in Array BlockingQueue need to acquire the same ReentrantLock exclusive lock before they can continue to execute. This greatly reduces throughput, and almost every operation will block other operations, most of which are queue entry and queue exit operations. Mutual exclusion. So Array Blocking Queue is not suitable for efficient data generation and consumption scenarios requiring high throughput. Array Blocking Queue is not used in common thread pools

If you have any questions, please point out that the author will revise them in time after verification. Thank you.

Topics: C++ JDK less jvm Java