High concurrency programming - queue - BlockingQueue-ArrayBlockingQueue

Posted by beckjo1 on Wed, 01 Dec 2021 21:20:35 +0100

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

  1. Throw exceptions add, remove, element
  2. Returns a result without throwing an exception offer poll peek
  3. 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;
}