Implementation and method analysis of blocking queue

queue

Queue is a special linear table. The special feature is that it only allows deletion at the front of the table and insertion at the back of the table. Like stack, queue is a linear table with restricted operation. The end of insertion is called tail end and the end of deletion is called queue head
The action of inserting a queue element into the queue is called queue in, and deleting a queue element from the queue is called queue out. Because the queue can only be inserted at one end and deleted at the other end, only the earliest element entering the queue can be deleted from the queue first. Therefore, the queue is also called FIFO – first in first out linear table

Blocking queue

  1. Support blocking insertion method: it means that when the queue is full, the queue will block the thread inserting elements and know that the queue is not full
  2. Supports the blocking removal method, which means that when the queue is empty, the thread getting the element will wait for the queue to become non empty

  using producer and consumer mode in concurrent programming can solve most concurrent problems. This mode can improve the overall data processing speed of the program by balancing the working capacity of production threads and consumer threads
   in the thread, the producer is the thread of production data, and the consumer is the thread of consumption data. In multi-threaded development, if the processing speed of the producer is greater than that of the consumer, the producer must wait for the consumer to process the data before continuing to produce data. If the processing capacity of the consumer is greater than that of the producer, the consumer must wait for the producer
In order to solve the problem of uneven production and consumption capacity, the producer and consumer model is proposed. The producer and consumer model solves the "strong coupling" problem between producers and consumers through a container. Producers and consumers do not communicate directly with each other, but communicate through the blocking queue. Therefore, after producing data, producers do not need to wait for consumers to process, but directly throw it to the blocking queue. Consumers do not ask producers for data, but directly get data from the blocking queue. The blocking queue is equivalent to a buffer, It balances the processing capacity of producers and consumers
  blocking queue is often used in the scenario of producers and consumers. Producers are the threads that add elements to the queue, consumers are the threads that take elements out of the queue, and blocking queue is the container used by producers to store elements and consumers to obtain elements

Processing methods \ methodsInsertion method  removal method    inspection method  
Throw exceptionadd(e)remove()element()
Return special valueoffer(e)poll()peek()
Continuous blockingput(e)take()nothing
Timeout exitoffer(e, time, unit)poll(time, unit)nothing
  • Throw exception: when the queue is full, if an element is inserted into the queue, an IllegalStateException ("Queuefull") exception will be thrown. When the queue is empty, the element obtained from the queue will throw a NoSuchElementException exception
  • Return special value: when inserting an element into the queue, it will return whether the element was successfully inserted. If the method is removed, it will take an element from the queue. If not, it will return null
  • Continuous blocking: when the blocking queue is full, if the producer thread put s elements into the queue, the queue will block the production thread until the queue is available or exits in response to an interrupt. When the queue is empty, if the consumer thread take s elements from the queue, the queue will block the consumer thread until the queue is not empty
  • Timeout exit: when the blocking queue is full, if the producer thread inserts elements into the queue, the queue will block the producer for a period of time. If the specified time is exceeded, the producer thread will exit
/***Class description: the function of fetching expired orders*/
public class FetchOrder implements Runnable {
	private DelayQueue<ItemVo<Order>> queue;

    public FetchOrder(DelayQueue<ItemVo<Order>> queue){
        this.queue = queue;

Common blocking queue

  • ArrayBlockingQueue: a bounded blocking queue consisting of an array structure
  • Linked blocking queue: a bounded blocking queue composed of linked list structure
  • PriorityBlockingQueue: an unbounded blocking queue that supports prioritization
  • DelayQueue: an unbounded blocking queue implemented using priority queue
  • Synchronous queue: a blocking queue that does not store elements
  • LinkedTransferQueue: an unbounded blocking queue composed of a linked list structure
  • Linked blocking deque: a bidirectional blocking queue composed of linked list structure

The above blocking queues all implement the BlockingQueue interface and are thread safe

Bounded blocking & & unbounded blocking

   finite queue means that the length is limited. When it is full, the producer will enter the blocking state. Unbounded queue means that unlimited things can be placed in it without being blocked due to the queue length limit. Of course, the space limit comes from the limitation of system resources. If it is not processed in time, the queue will become larger and larger, exceeding a certain limit, resulting in memory overrun, The operating system or JVM will kill the OOM directly
   it should be noted that unbounded will also cause blocking, because blocking is not only reflected in the blocking when producers put in elements, but also when consumers put in elements, if there are no elements, they will also block

ArrayBlockingQueue

   ArrayBlockingQueue is a bounded blocking queue implemented by array. This queue sorts the elements according to the first in first out (FIFO) principle. By default, it is not guaranteed that threads can access the queue fairly. The so-called fair access queue is that threads blocked by value can access the queue according to the blocking order, that is, threads blocked first can access the queue first, Unfairness is unfair to the threads waiting first. When the queue is available, blocked threads can compete for the qualification to access the queue. It is possible to block the thread first and then access the queue. Parameters can be set during initialization

LinkedBlockingQueue

   LinkedBlockingQueue is a bounded blocking queue implemented by linked list. The default and maximum length of this queue is Inreger.MAX_VALUE, this queue sorts the elements according to the first in first out principle

The difference between Array implementation and Linked implementation

  1. The implementation of locks in queues is different

  the locks in the queue implemented by ArrayBlockingQueue are not separated, that is, production and consumption use the same lock
  the locks in the queue implemented by LinkedBlockingQueue are separated, that is, putLock is used for production and TakeLock is used for consumption

  1. Different operations in production or consumption

  in the queue implemented by ArrayBlockingQueue, enumeration objects are directly inserted or removed during production and consumption
   in the queue implemented by LinkedBlockingQueue, during production and consumption, the enumeration object needs to be converted into a Node for insertion or removal, which will affect the performance

  1. The queue size is initialized differently

  the size of the queue must be specified in the queue implemented by ArrayBlockingQueue
   the size of the queue can not be specified in the queue implemented by LinkedBlockingQueue, but the default is Integer.MAX_VALUE

PriorityBlockingQueue

  PriorityBlockingQueue is an unbounded blocking queue that supports priority. By default, elements are arranged in natural ascending order. You can also customize the class to implement the compareTo() method (Comparator) to specify the element sorting rule, or specify the construction parameter Comparator to sort the elements when initializing PriorityBlockingQueue. It should be noted that the order of elements with the same priority cannot be guaranteed.

DelayQueue

   DelayQueue is an unbounded blocking queue that supports Delayed acquisition of elements. The queue is implemented by PriorityQueue. The elements in the queue must implement the Delayed interface. When creating elements, you can specify how long to obtain the current elements from the queue. Elements can be extracted from the queue only when the delay period expires

Application scenario:
Design of stand-alone cache system: you can use DelayQueue to save the validity period of cache elements, and use a thread to query DelayQueue circularly. Once the element can be obtained from the DelayQueue blocking queue, it indicates that the validity period of the element has expired

Experiment code:

Class description: the function of fetching expired orders:

/**
 *Class description: the function of fetching expired orders
 */
public class FetchOrder implements Runnable {
	private DelayQueue<ItemVo<Order>> queue;
	
    public FetchOrder(DelayQueue<ItemVo<Order>> queue){
        this.queue = queue;
    }

	@Override
	public void run() {
		while(true) {
			try {
				ItemVo<Order> item = queue.take();
				Order order = (Order)item.getData();
				System.out.println("Get From Queue:"+"data=" +order.getOrderNo()+";"+order.getOrderMoney());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}	
}

Class description: stored queue elements:

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 *Class description: the element of the stored queue,
 */
public class ItemVo<T> implements Delayed {

	//Expiration time, but the value passed in represents the expiration time, in milliseconds
	private long activeTime;
	private T data;//Business data, generic

	//Incoming expiration duration, in seconds, internal conversion
	public ItemVo(long expirationTime, T data) {
		this.activeTime = expirationTime*1000+System.currentTimeMillis();
		this.data = data;
	}

	public long getActiveTime() {
		return activeTime;
	}

	public T getData() {
		return data;
	}

	/*
	 * This method returns the remaining time of the activation date. The time unit is specified by the unit parameter.
	 */
	public long getDelay(TimeUnit unit) {
		long d = unit.convert(this.activeTime
				-System.currentTimeMillis(),unit);
		return d;
	}

	/*
	 *Delayed The interface inherits the Comparable interface and is sorted according to the remaining time. The actual calculation accuracy is nanoseconds
	 */
	public int compareTo(Delayed o) {
		long d = (getDelay(TimeUnit.MILLISECONDS)
				- o.getDelay(TimeUnit.MILLISECONDS));
		if (d==0){
			return 0;
		}else{
			if (d<0){
				return -1;
			}else{
				return  1;
			}
		}
	}

}

Class description: entity class of the order:

/**
 *Class description: entity class of the order
 */
public class Order {
	private final String orderNo;//Order number
	private final double orderMoney;//Amount of the order
	
	public Order(String orderNo, double orderMoney) {
		super();
		this.orderNo = orderNo;
		this.orderMoney = orderMoney;
	}

	public String getOrderNo() {
		return orderNo;
	}

	public double getOrderMoney() {
		return orderMoney;
	}
	
	
	
}

Class description: push order into queue:

import java.util.concurrent.DelayQueue;

/**
 *Class description: push order into queue
 */
public class PutOrder implements Runnable {
    private DelayQueue<ItemVo<Order>> queue;

    public PutOrder(DelayQueue<ItemVo<Order>> queue){
        this.queue = queue;
    }

	@Override
	public void run() {
		//Expires in 5 seconds
		Order orderTb = new Order("Tb12345",366);
		ItemVo<Order> itemTb = new ItemVo<Order>(5,orderTb);
		queue.offer(itemTb);
		System.out.println("Timeout after 5 seconds of order:"+orderTb.getOrderNo()+";"
                +orderTb.getOrderMoney());
		//Expires in 8 seconds
		Order orderJd = new Order("Jd54321",366);
		ItemVo<Order> itemJd = new ItemVo<Order>(8,orderJd);
		queue.offer(itemJd);
		System.out.println("Timeout after 8 seconds of order:"+orderJd.getOrderNo()+";"
                +orderJd.getOrderMoney());

	}	
}

Class description: delay queue test procedure:

import java.util.concurrent.DelayQueue;

/**
 *Class description: delay queue test program
 */
public class Test {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<ItemVo<Order>> queue = new DelayQueue<ItemVo<Order>>();//Delay queue

        new Thread(new PutOrder(queue)).start();
        new Thread(new FetchOrder(queue)).start();

        //Print a number every 500 milliseconds
        for(int i=1;i<15;i++){
            Thread.sleep(500);
            System.out.println(i*500);
        }
    }
}

SynchronousQueue

  synchronous queue is a blocking queue that does not store elements. Each put operation must wait for a take operation (put and take are methods for continuous output), otherwise elements cannot be added. The synchronous queue can be regarded as a passer who is responsible for directly passing the data processed by the producer thread to the consumer thread. The queue itself does not store any elements, which is very suitable for transitivity scenarios. The throughput of synchronous queue is higher than LinkedBlockingQueue and ArrayBlockingQueue

LinkedTransferQueue

Linkedtransgerqueue main methods:

transfer()

    if a consumer is currently waiting to receive an element (when the consumer uses the take() method or the poll() method with time limit), transfer() can immediately transmit the element passed in by the producer to the consumer. If no consumer is waiting to receive an element, transfer() will store the element in the tail node of the queue and return it after the element is consumed by the consumer

tryTransfer()

   tryTransfer() is used to test whether the producer or the incoming element can be directly passed to the consumer. If there is no consumer waiting to receive the element, it returns false. The difference between tryTransfer() and transfer() is that the method returns immediately regardless of whether the consumer accepts it or not, while transfer() returns only after the consumer has consumed it

LinkedBlockingDeque

  LinkedBlockingDeque is a bidirectional blocking queue composed of linked list structure. The so-called two-way queue means that elements can be inserted and removed from both ends of the queue. Because there is one more entry to the two-way queue, when multiple threads join the queue at the same time, the competition is reduced by half.
addFirst, addLast, offerFirst, offerLast, peekFirst, peekLast and other methods are added. The method ending with the word First represents inserting, obtaining (peek) or removing the First element of the double ended queue. A method ending with the Last word that inserts, gets, or removes the Last element of a double ended queue. In addition, the insert method add is equivalent to addLast, and the remove method remove is equivalent to removeFirst. However, the take method is equivalent to takeFirst. I don't know if it's a JDK bug. It's clearer to use the method with First and Last suffixes. When initializing LinkedBlockingDeque, you can set the capacity to prevent it from over expanding. In addition, bidirectional blocking queue can be used in "work stealing" mode

Tags: Java Back-end

Posted on Thu, 21 Oct 2021 23:50:48 -0400 by toro04