problem
(1) How is Array BlockingQueue implemented?
(2) Does Array Blocking Queue need to be expanded?
(3) What are the disadvantages of Array Blocking Queue?
brief introduction
Array BlockingQueue is the next block queue implemented in arrays in java Concurrent packages. It is thread-safe. See the following analysis for expansion.
queue
Queues, is a linear table, which is characterized by FIFO, or FIFO, as we usually queue, first come first served, that is, first in the queue first out.
Source code analysis
Main attributes
// Storing elements using arrays final Object[] items; // A pointer to an element int takeIndex; // Pointer for Placing Elements int putIndex; // Element quantity int count; // Locks guaranteeing concurrent access final ReentrantLock lock; // Non empty condition private final Condition notEmpty; // Non full condition private final Condition notFull;
Through attributes, we can get the following important information:
(1) Storing elements with arrays;
(2) Mark the position of the next operation by placing and fetching the pointer;
(3) Using re-entry lock to ensure concurrent security;
Main construction methods
public ArrayBlockingQueue(int capacity) { this(capacity, false); } public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); // Initialize arrays this.items = new Object[capacity]; // Creating reentrant locks and two conditions lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
Through the construction method, we can draw the following two conclusions:
(1) When Array BlockingQueue is initialized, it must pass in capacity, that is, the size of the array;
(2) We can control whether the type of re-entry lock is fair lock or unfair lock by constructing method.
Join the team
There are four ways to join the team. They are add (E), offer (E), put (E), offer (E), long time out (E), Time Unit unit. What's the difference between them?
public boolean add(E e) { // Call the add(e) method of the parent class return super.add(e); } // super.add(e) public boolean add(E e) { // Call offer(e) if it returns true successfully, and throw an exception if it fails if (offer(e)) return true; else throw new IllegalStateException("Queue full"); } public boolean offer(E e) { // Elements cannot be empty checkNotNull(e); final ReentrantLock lock = this.lock; // Lock up lock.lock(); try { if (count == items.length) // If the array is full, return false return false; else { // If the array is not full, call the queue entry method and return true enqueue(e); return true; } } finally { // Unlock lock.unlock(); } } public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; // Lock, throw an exception if the thread interrupts lock.lockInterruptibly(); try { // If the array is full, use notFull to wait // notFull waits, which means that the queue is full now. // The queue is not satisfied until one element is removed. // Then wake up notFull and continue with the current logic. // Here's why you use while instead of if // Because it's possible to have multiple threads blocking on lock // Even if it wakes up, other threads may modify the queue one step at a time and become full again. // It's time to wait again. while (count == items.length) notFull.await(); // Join the team enqueue(e); } finally { // Unlock lock.unlock(); } } public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { checkNotNull(e); long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; // Lock up lock.lockInterruptibly(); try { // If the array is full, block nanos nanoseconds // If there is still no space to wake up the thread and time is up, return false. while (count == items.length) { if (nanos <= 0) return false; nanos = notFull.awaitNanos(nanos); } // Join the team enqueue(e); return true; } finally { // Unlock lock.unlock(); } } private void enqueue(E x) { final Object[] items = this.items; // Place the element directly in the pointer position items[putIndex] = x; // If the pointer is placed at the end of the array, it returns to the head. if (++putIndex == items.length) putIndex = 0; // Quantity plus 1 count++; // Wake up notEmpty, because there's an element in the team, so it's definitely not empty. notEmpty.signal(); }
(1) throw an exception if the queue is full when adding (e);
(2) When offer(e), return false if the queue is full;
(3) If the queue is full, use notFull to wait.
(4) When offer(e, timeout, unit) is full, wait for a period of time and return false if the queue is still full.
(5) Store elements by placing pointers and recycling arrays;
Team out
There are four ways to get out. They are remove(), poll(), take(), poll (long time out, Time Unit unit). What's the difference between them?
public E remove() { // Call poll() method to queue E x = poll(); if (x != null) // If an element is out of line, return the element. return x; else // Throw an exception if no element is out of line throw new NoSuchElementException(); } public E poll() { final ReentrantLock lock = this.lock; // Lock up lock.lock(); try { // If there are no elements in the queue, return null, otherwise queue return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; // Lock up lock.lockInterruptibly(); try { // If the queue has no elements, the blocking waits on condition notEmpty while (count == 0) notEmpty.await(); // Be elementary and get out of the team again return dequeue(); } finally { // Unlock lock.unlock(); } } public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; // Lock up lock.lockInterruptibly(); try { // If the queue has no elements, the blocking waits for nanos nanoseconds // If the next thread gets a lock but the queue is still elemental and has timed out, it returns null while (count == 0) { if (nanos <= 0) return null; nanos = notEmpty.awaitNanos(nanos); } return dequeue(); } finally { lock.unlock(); } } private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") // Take the element of the pointer position E x = (E) items[takeIndex]; // Set the pointer position to null items[takeIndex] = null; // Take the pointer forward, and if the array ends, return to the front end of the array for recycling if (++takeIndex == items.length) takeIndex = 0; // Number of elements minus 1 count--; if (itrs != null) itrs.elementDequeued(); // Wake up notFull condition notFull.signal(); return x; }
(1) If the queue is empty when remove(), an exception is thrown.
(2) When poll() returns null if the queue is empty;
(3) If the queue is empty when take(), it will block and wait on condition notEmpty.
(4) When poll(timeout, unit) is empty, the queue is blocked and waits for a period of time, and then returns null if it is still empty.
(5) Use the pointer loop to extract elements from the array;
summary
(1) Array BlockingQueue does not need to be expanded because it specifies capacity at initialization and recycles arrays;
(2) Array BlockingQueue recycles arrays using takeIndex and putIndex;
(3) Entry and exit methods are defined to meet different purposes.
(4) Using re-entry lock and two conditions to ensure concurrent security;
Egg
(1) On the methods in Blocking Queue?
BlockingQueue is the top-level interface for all blocking queues. It defines a set of methods. What's the difference between them?
operation | throw | Returns a specific value | block | overtime |
---|---|---|---|---|
Join the team | add(e) | offer(e)-false | put(e) | offer(e, timeout, unit) |
Team out | remove() | poll()-null | take() | poll(timeout, unit) |
inspect | element() | peek()-null | - | - |
(2) What are the disadvantages of Array Blocking Queue?
(a) The queue length is fixed and must be specified at initialization, so the capacity must be carefully considered before using it;
b) If the speed of consumption does not keep up with the speed of queue entry, the threads of providers will be blocked all the time, and the more blocked, the more dangerous it is.
c) Only one lock is used to control entry and exit, which is inefficient. Is it possible to split the entry and exit into two locks with the help of the idea of segmentation? And listen to the next decomposition.
Welcome to pay attention to my public number "Tong Ge Reads Source Code". Check out more articles about source code series and enjoy the sea of source code with Tong Ge.