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:
- Array BlockingQueue inherits from AbstractQueue and implements the BlockingQueue interface
- 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
- 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.
- 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:
- The iterator Itr created saves the status of the queue at that time (no queue element values are saved)
- Created iterators are put into Itrs (Iterator Maintenance List) for unified maintenance and management
- 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
- The iterator maintenance list Itrs is updated by itrs.elementDequeued() during the queue operation
- 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.
- 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.