High concurrency programming - queue - BlockingQueue-ArrayBlockingQueue
Before introducing ArrayBlockingQueue, in order to make everyone better understand this knowledge point, first make a brief introduction to the relevant knowledge of Queue and BlockingQueue
1, Queue queue interface
Queue inherits from the Collection data set. There are six main methods in queue. The following is a brief introduction to the six methods in turn
public interface Queue<E> extends Collection<E>{ //This method is to add elements to the queue. If there is enough space in the queue, the elements will be inserted into the queue and returned true,If there is not enough space, it will be thrown IllegalStateException abnormal boolean add(E e); //Add an element to the queue. If the addition is successful, the result is returned true,If the queue is full, return false boolean offer(E e); //Delete the queue head element and delete it. If the queue is empty, it will be thrown NoSuchElementException abnormal E remove(); // Returns and deletes the queue head element. If the queue is empty, returns null E poll(); //Returns the header element but does not delete it. If the queue is empty, an exception is thrown NoSuchElementException E element(); // Returns the header element but does not delete it. If the queue is empty, returns null E peek(); }
2, BlockingQueue interface
BlockingQueue is called blocking Queue. This Queue inherits from Queue, so it inherits the methods in Queue and extends its own two additional operation queues
- The in put method of expenditure blocking. When the queue is full, it will block the insertion queue until the queue elements are consumed
- take, the removal method of expenditure blocking. When the queue is empty, the removal thread will wait until the queue is not empty before activating the elements in the consumption queue
public interface BlockingQueue<E> extends Queue<E> { /** *ditto*/ boolean add(E e); /** *ditto*/ boolean offer(E e); /** *When the queue is not full, it is inserted normally. When the queue is full, it is blocked. Insert data again until the queue is not satisfied, and interrupt is supported*/ void put(E e) throws InterruptedException; /** * Insert data into the queue and set the blocking time. If no data is inserted after the blocking time, false is returned*/ boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; /** * If there is data in the queue, get the data and delete it. If there is no data in the queue, block and wait until there is data to get the data again*/ E take() throws InterruptedException; /** *Get the data in the queue. If there is data, get the data. If there is no data, block. If the blocking time is exceeded, return null*/ E poll(long timeout, TimeUnit unit) throws InterruptedException; /** * ditto*/ int poll(); /** * ditto*/ boolean remove(Object o); }
BlockingQueue method summary
If the queue data is full or empty, the results returned by each method are summarized
- Throw exceptions add, remove, element
- Returns a result without throwing an exception offer poll peek
- Block put take
3, ArrayBlockingQueue
ArrayBlockingQueue is a queue with limited space, also known as bounded queue, so the capacity needs to be determined during initialization. The bottom layer adopts array data storage, and ReentrantLock is used to ensure its thread safety.
Usage scenario: the mode used for producers and consumers. When the speeds of producers and consumers basically match, it is more appropriate to select ArrayBlockingQueue. If the speed of consumers is greater than that of producers and the queue is empty, the consumer thread will be blocked, otherwise the producer will be blocked.
Concurrent processing: the ReentrantLock lock is used in the ArrayBlockingQueue, and the same lock is used when entering and leaving the queue. The problem is that the execution cannot be performed concurrently when entering and leaving the queue, and the performance is not very good under extremely high concurrency
Introduction to the source code of ArrayBlockingQueue
The internal is mainly a ReentrantLock lock, which generates two conditional queues notFull notFull
//data elements final Object[] items; //Consume the next element int takeIndex; //Insert next element int putIndex; //Total number of elements int count; //Define global locks final ReentrantLock lock; //When the queue is empty, the consumer needs to wait private final Condition notEmpty; //When the queue is full, the producer needs to wait private final Condition notFull; //Construction method, fair If no data is transmitted, the default value is false public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); //Define data capitalization this.items = new Object[capacity]; //Create a global lock, false The default is non fair lock, true Fair lock lock = new ReentrantLock(fair); //Condition queue for obtaining lock notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
Introduction to the source code when put ting data
//Blocking queue insertion public void put(E e) throws InterruptedException { //Check whether the element is empty. If it is empty, throw an exception directly checkNotNull(e); //Acquire lock final ReentrantLock lock = this.lock; //Add interrupt lock lock.lockInterruptibly(); try { //The number of elements is equal to the length of the array, indicating that the queue is full and the producer is blocked while (count == items.length) notFull.await(); //If not, put it in the queue enqueue(e); } finally { //Release lock lock.unlock(); } } //Queue operation private void enqueue(E x) { // Get current queue final Object[] items = this.items; //Place the element in the next cell of the array items[putIndex] = x; //When the data is full, point to the head of the array, the ring array if (++putIndex == items.length) putIndex = 0; count++; //Wake up blocked consumers notEmpty.signal(); }
Out of line take method
//Consumption data public E take() throws InterruptedException { //Acquire lock final ReentrantLock lock = this.lock; //Add interrupt lock lock.lockInterruptibly(); try { //If there is no data,Consumer blocking while (count == 0) notEmpty.await(); //Otherwise, the consumption data is returned and deleted return dequeue(); } finally { lock.unlock(); } } private E dequeue() { //Get data queue final Object[] items = this.items; @SuppressWarnings("unchecked") //obtain takeindex Element of location E x = (E) items[takeIndex]; //Empty the array of corresponding positions items[takeIndex] = null; //If the ring array comes to the end, it will point to the first place in the second if (++takeIndex == items.length) takeIndex = 0; //Total minus count--; if (itrs != null) itrs.elementDequeued(); //Wake up production thread notFull.signal(); return x; }