java blocking queue

Keywords: Java less JDK

Blocking queues can block the current thread, such as a thread fetching elements from an empty blocking queue, where the thread is blocked until there are elements in the blocking queue. When there are elements in the queue, the blocked thread is automatically awakened without notify. This provides great convenience.

I. Several main blocking queues

Since Java 1.5, several blocking queues have been provided under the java.util.concurrent package, mainly as follows:

  • Array Blocking Queue: A bounded blocking queue supported by an array. This queue sorts elements according to FIFO (first in first out) principle. The head of the queue is the element that has existed in the queue for the longest time. At the end of the queue is the element that has the shortest time in the queue. New elements are inserted at the end of the queue, and the queue fetch operation starts at the head of the queue.
    This is a typical "bounded buffer" in which fixed-size arrays hold elements inserted by the producer and elements extracted by the user. Once such a buffer is created, its capacity cannot be increased. Trying to put elements into a full queue results in blocking operations; trying to extract elements from an empty queue results in similar blockages.
    Such an optional fairness policy supports sorting of waiting producer threads and user threads. By default, this sort is not guaranteed. However, queues constructed by setting fairness to true allow threads to be accessed in FIFO order. Fairness usually reduces throughput, but also reduces variability and avoids "imbalance".

  • Linked Blocking Queue: A blocking queue with arbitrary range based on linked nodes. This queue sorts elements by FIFO (first in first out). The head of the queue is the longest element in the queue. The tail of the queue is the shortest element in the queue. The new element is inserted at the end of the queue, and the queue capture operation gets the element at the head of the queue. Link queues usually have higher throughput than array-based queues, but in most concurrent applications, their predictable performance is lower.
    Optional capacity range construction method parameters are used as a method to prevent queue overexpansion. If no capacity is specified, it is equal to Integer.MAX_VALUE. Unless inserting nodes causes queues to exceed capacity, link nodes are created dynamically after each insertion.
    This class and its iterators implement all options for Collection and Iterator interfaces.

  • Priority BlockingQueue: The above two queues are first-in-first-out queues, but Priority BlockingQueue is not an unbounded blocking queue. It uses the same sequence rules as PriorityQueue and provides blocking access operations. Although this queue is logically unbounded, attempts to perform add operations when resources are exhausted will also fail (leading to OutOfMemoryError). This class does not allow null elements. Priority queues that depend on natural order also do not allow insertion of uncomparable objects (which causes a ClassCastException to be thrown).
    This class and its iterators implement all the alternatives to Collection and Iterator interfaces. The iterator provided in the iterator() method does not guarantee that elements of PriorityBlockingQueue are traversed in a specific order. If you need to traverse orderly, you should consider using Arrays.sort(pq.toArray()). In addition, you can use the drainTo method to remove all or part of the elements in priority order and place them in another collection.
    Operations on this type do not guarantee the order of elements with the same priority. If a sort needs to be performed, a custom class or comparator can be defined, and the comparator can use the modifier key to disconnect the primary priority values.

  • DelayQueue: An unbounded blocking queue of Delayed elements that can only be extracted when the delay expires. The head of the queue is the Delayed element that lasts the longest after the delay expires. If the delay has not expired, the queue has no head, and poll returns null. When the getDelay(TimeUnit.NANOSECONDS) method of an element returns a value less than or equal to zero, the expiration occurs. Even if you can't use take or poll to remove unexpired elements, you won't treat them as normal elements. For example, the size method returns the count of both expired and unexpired elements. Null elements are not allowed in this queue.
    This class and its iterators implement all the options for Collection and Iterator interfaces.

2. Implementation Principle of Blocking Queue

If the queue is empty, the consumer will wait all the time. When the producer adds elements, how does the consumer know that the current queue has elements? JDK is implemented in notification mode. The so-called notification mode is that when the producer adds elements to the full queue, the producer will be blocked. When the consumer consumes elements in the queue, the producer will be informed that the current queue is available.

III. Example comparison

  • Example 1: Producer-consumer mode implemented with blocking queues
package com.bh.block;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**Producer-consumer model implemented with blocking queues
 * @author bh
 *  */
public class Test {

    private int queueSize = 10;
    /*A bounded blocking queue supported by an array. This queue sorts elements according to FIFO (first in first out) principle.
    The head of the queue is the element that has existed in the queue for the longest time. At the end of the queue is the element that has the shortest time in the queue.
    */
    private BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(queueSize);

    public static void main(String[] args) {

        Test test = new Test();
        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();

        producer.start();
        consumer.start();
    }
    /**Consumer
     * @author bh
     *
     */
    class Consumer extends Thread{

        @Override
        public void run() {
            consume();
        }
        /**
         * consumption
         */
        private void consume() {

            while(true){
                try {
                    queue.take();
                    System.out.println("Take an element from the queue, and the queue remains"+queue.size()+"Element");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**Producer
     * @author bh
     *
     */
    class Producer extends Thread{

        @Override
        public void run() {
            produce();
        }
        /**
         * production
         */
        private void produce() {

            while(true){
                try {
                    queue.put(1);
                    System.out.println("Insert an element into the queue fetch, leaving the queue space:"+(queueSize-queue.size()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • Example 2: Producer-consumer mode implemented with non-blocking queues
package com.bh.unblock;

import java.util.PriorityQueue;
import java.util.Queue;

/**Non-blocking queues implement producer and consumer patterns by using Object.wait() and Object.notify() methods
 * @author bh
 *
 */
public class Test {

    private int queueSize = 10;
    //Unbounded priority queue based on priority heap
    private Queue<Integer> queue = new PriorityQueue<Integer>(queueSize);

    public static void main(String[] args) {

        Test test = new Test();
        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();

        producer.start();
        consumer.start();
    }

    /**Consumer
     * @author bh
     *
     */
    class Consumer extends Thread{

        @Override
        public void run() {
            consume();
        }
        /**consumption
         */
        private void consume() {

            while(true){
                synchronized (queue) {
                    while(queue.size() == 0){
                        try {
                            System.out.println("Queue empty, waiting for data");
                            //Causes the current thread to wait before other threads call the notify() method or notifyAll() method of this object
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.poll(); //Every time the head element is removed
                    queue.notify(); //Wake up a single thread waiting on this object monitor
                    System.out.println("Take an element from the queue, and the queue remains"+queue.size()+"Element");
                }
            }
        }
    }

    /**Producer
     * @author bh
     *
     */
    class Producer extends Thread{

        @Override
        public void run() {
            produce();
        }
        /**production
         */
        private void produce() {

            while(true){
                synchronized (queue) {
                    while(queue.size() == queueSize){
                        try {
                            System.out.println("The queue is full, waiting for free space");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.offer(1); //Insert one element at a time
                    queue.notify();
                    System.out.println("Insert an element into the queue fetch, leaving the queue space:"+(queueSize-queue.size()));
                }
            }
        }
    }
}

Posted by creativodev on Thu, 21 Mar 2019 23:45:52 -0700