Java blocking queue BlockingQueue (producer consumer model)

Posted by rr1024 on Tue, 28 Dec 2021 07:39:19 +0100

Blocking queue

Blocking queue is a special queue. JDK provides many kinds of blocking queues. However, LinkedBlockingDeque and PriorityBlockingQueue are common. They both implement the BlockingQueue interface. Here, LinkedBlockingDeque is mainly used to implement the producer consumer model.



LinkedBlockingDeque is a bounded blocking queue implemented with a linked list. The default and maximum length of this queue is integer MAX_ VALUE. This queue follows the first in, first out principle.
put and tag methods are provided in the blocking queue. These two methods have blocking function.

  • The put method is used to enter the queue. If the queue is full, it will block until space is available
  • The take method is used to get out of the queue. If the queue is empty, it will be blocked until there are elements in the queue
  • BlockingQueue also has offer, poll, peek and other methods, but these methods do not have blocking characteristics

Producer consumer model

The producer consumer design pattern can be realized by blocking the queue. This pattern separates the two processes of "finding out the work to be completed" and "executing the work", and puts the work items into a "to be completed" list for subsequent processing, rather than processing immediately after finding out. The producer consumer model can simplify the development process because it eliminates the code dependency between producer and consumer classes. In addition, the model decouples the process of production data from the use process to simplify workload management.

In the producer consumer model based on blocking queue, when the data is generated, the producer puts the data into the queue, and when the consumer is ready to process the data, it will obtain the data from the queue. The producer does not need to know the identification or quantity of consumers, nor does it need to know whether they are the only producer, but only needs to put the data into the queue. Similarly, consumers don't need to know who the producer is. Just get the data from the queue.

for instance:
Taking two people washing dishes as an example, their division of labor is also a producer consumer model; One of them put the washed dishes on the shelf, while the other took the dishes out of the shelf and dried them.
In this example, the shelf is equivalent to blocking the queue. If there are no plates on the shelf, the consumer will wait until there are plates on the shelf to dry. If the shelf is full, the manufacturer will stop cleaning until there is more space on the shelf,. We can extend this analogy to multiple producers and multiple consumers, and each worker needs to deal with shelves. People don't need to know how many producers or consumers there are.

Another example:
The Three Gorges Dam in our country will close the gate to store the water in the upstream during the flood season and open the gate to release water appropriately during the dry season, so as to ensure that there will be no flood and drought in the downstream.
In this column, the upstream is the producer, the downstream is the consumer, and the dam is the blocking queue.

Producer consumer model is a very common programming method in re server development.
It has two greatest uses

  1. Decoupling

Just like the above example, producers and consumers do not need to know who each other is, but only need to do their own things. If there are multiple producers and consumers, it will not affect them, and it will be easier to expand in the future.

  1. Peak cutting and valley filling

If a large wave of requests hits our server, we can also ensure that subsequent servers still consume data at a fixed rate by blocking the queue

Implement producer consumer model

  1. Create a blocking queue first
  2. Create two more threads, a producer thread and a consumer thread
  3. Then start the thread

It is relatively simple to directly use the built-in LinkedBlockingQueue class to implement the producer consumer model

public static void main(String[] args) throws InterruptedException {
        //The specified blocking queue capacity is 100
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(100);
        //put has blocking function, and offer has no blocking function
        queue.put(10);
        //take has blocking function, and poll has no blocking function
        queue.take();
        //Producer thread
        Thread producer = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    try {
                        //Thread.sleep(1000);
                        queue.put(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Produced "+i);
                }
            }
        });
        producer.start();
        //Consumer thread
        Thread consumer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                        Integer value = queue.take();
                        System.out.println("Consumption "+value);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        consumer.start();

        try {
            producer.join();
            consumer.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Simulation Implementation of blocking queue

A circular queue can be realized through an array, and a simple blocking queue can be realized by locking

static class LinkedBlockingQueue {
        private int start;
        private int end;
        private int[] array;
        //Number of blocking queue elements
        private int size;
        //Lock object
        Object lock;

        public LinkedBlockingQueue() {
            this.array = new int[Integer.MAX_VALUE];
            this.lock = new Object();
        }
        public LinkedBlockingQueue(int capacity) {
            this.array = new int[capacity];
            this.lock = new Object();
        }

        public void put(int value) throws InterruptedException {
            synchronized (lock) {
                //Determine whether the queue is full
                while (this.size == this.array.length) {
                    lock.wait();
                }
                this.array[this.end++] = value;
                this.size++;
                //Determine whether end has reached the end of the array
                if (this.end == this.array.length) {
                    this.end = 0;
                }
                //Each insert wakes up one thread
                lock.notify();
            }
        }
        public int tack() throws InterruptedException {
            synchronized (lock) {
                //Determine whether the queue is empty
                while (this.size == 0) {
                    lock.wait();
                }
                int value = this.array[this.start++];
                this.size--;
                //Determine whether start goes to the end of the array
                if (this.start == this.array.length) {
                    this.start = 0;
                }
                //Wake up a thread
                lock.notify();
                return value;
            }
        }

Topics: Java Back-end Multithreading