Vernacular allows you to understand what the consumer producer model is and what the thread pool is

Posted by chancho on Thu, 09 Dec 2021 15:11:36 +0100

Chapter 1 thread waiting and notification

Section 01 method introduction

Methods in Object class
  • wait() puts the current thread into a wait state until it is notified
  • wait(long) makes the current thread enter the waiting state and sets the time at the same time; Until notified or the end of time
  • notify() notifies a waiting thread at random
  • notifyAll() notifies all waiting threads

Note: the wait and notification methods must be lock objects, otherwise the IllegalMonitorStateException will be thrown

Section 02 case codes

/**
 * Wait for the thread through the lock object, and notify the thread to execute after 5 seconds
 */
public class WaitDemo {

    public synchronized void print() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" +i);
            if(i == 50){
                //Make the current thread wait
                this.wait();
            }
        }
    }

    public synchronized void notifyTest(){
        //Let the waiting thread execute
        this.notifyAll();
    }

    public static void main(String[] args) {
        WaitDemo waitDemo = new WaitDemo();
        new Thread(()->{
            try {
                waitDemo.print();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        waitDemo.notifyTest();
    }
}

Section 03 differences between wait and sleep

  1. Call object is different

    wait() is called by the lock object

    sleep() is called by a thread

  2. Locks are used differently

    After the wait is executed, the lock is automatically released

    After sleep is executed, the lock will not be released automatically

  3. Different wake-up mechanisms

    After executing wait, you can be notified to wake up

    After executing sleep, you can only wait for the time to end and wake up automatically

Chapter II producer consumer model

Section 01 general

A design pattern that does not belong to GOF23

  • producer

    Some programs / processes / threads are responsible for production, and the data belongs to the producer

  • consumer

    Some programs / processes / threads are responsible for using data, which belongs to consumers

Section 02 issues between producers and consumers

  1. Problem introduction
  • High coupling and close contact between producers and consumers are not conducive to the expansion and maintenance of the system
  • Low concurrency performance and less requests can be processed at the same time
  • Uneven busy and idle, inconsistent speed between producers and consumers, resulting in a waste of system resources
  1. Solution

Therefore, we can set a data buffer so that the data produced by the producer can be stored in the buffer, and then the consumer can get the data in the buffer, which reduces the coupling between the producer and the consumer. Secondly, we can also set a fixed maximum capacity value for the buffer. When the data reaches the maximum value, the producer will wait and wake up the producer when there is free space in the buffer. Similarly, when the buffer data is empty, the consumer enters the wait, and then wakes up the consumer thread when the buffer data is not empty, which can solve the problem of inconsistent speed between producers and consumers and waste of system resources.

Section 03 case codes

The program simulates a steamed stuffed bun shop, a producer thread produces 100 steamed stuffed buns, 10 consumer threads, and each consumer thread consumes 10 steamed stuffed buns.

import java.util.ArrayList;
import java.util.List;

/**
 * Steamed stuffed bun shop
 */
public class BaoziShop {
    /**
     * steamed stuffed bun
     */
    class Baozi{
        private int id;
        public Baozi(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "steamed stuffed bun--" + id;
        }
    }
    //upper limit
    public static final int MAX_COUNT = 100;
    //Buffer to store data
    private List<Baozi> baozis = new ArrayList<>();

    /**
     * Make steamed stuffed bun
     */
    public synchronized void makeBaozi() throws InterruptedException {
        //Determine whether the buffer is full
        if(baozis.size() == MAX_COUNT){
            System.out.printf("The buffer is full,%s wait for%n",Thread.currentThread().getName());
            //Let the producer thread wait
            this.wait();
        }else{
            //Notify producer thread production
            this.notifyAll();
        }
        //Create steamed stuffed bun
        Baozi baozi = new Baozi(baozis.size() + 1);
        System.out.println(Thread.currentThread().getName()+"Yes"+baozi);
        //Save to buffer
        baozis.add(baozi);
    }

    /**
     * Take the steamed stuffed bun
     */
    public synchronized void takeBaozi() throws InterruptedException {
        //Determine whether the buffer is empty
        if(baozis.size() == 0){
            System.out.printf("The buffer is empty,%s wait for%n", Thread.currentThread().getName());
            //Let consumers wait
            this.wait();
        }else{
            //Inform consumers of consumption
            this.notifyAll();
        }
        //Get the first bun and delete it
        if(baozis.size() > 0){
            Baozi baozi = baozis.remove(0);
            System.out.println(Thread.currentThread().getName()+"Yes"+baozi);
        }
    }

    public static void main(String[] args) {
        BaoziShop baoziShop = new BaoziShop();
        //A producer
        Thread productor = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    baoziShop.makeBaozi();
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        productor.start();
        //10 consumers eat 10 steamed stuffed buns
        for (int i = 0; i < 10; i++) {
            Thread consumer = new Thread(() ->{
                try {
                    for (int j = 0; j < 10; j++) {
                        baoziShop.takeBaozi();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            consumer.start();
        }
    }
}

Application scenario: it can be applied to cases of production resources and consumption resources

  • 12306 ticketing
  • Message queue
  • Thread pool
  • wait

Chapter 3 blocking queue

Section 01 problem introduction

What is a blocking queue and why is there a message queue. We can find a problem through the case of selling steamed stuffed buns above. Our buffer is an ArrayList created by us. We need to manually write code logic to judge whether the data in the buffer is full or empty. This is very cumbersome. Is there any cache container that can help us solve this problem and automatically wait and notify threads according to whether the data is full or empty. In this way, we have the concept of blocking queue.

Section 02 getting started

BlockingQueue interface

  • put adds data. When the upper limit is reached, the thread will automatically wait
  • take and delete the data. If the data is empty, the thread will automatically wait

Implementation class

The data structure of ArrayBlockingQueue class is array

LinkedBlockingQueue class linked list structure

Section 03 case codes

We just analyzed the traditional way of using ArrayList as the pain point of buffer container. Now we can use blocking queue to help us solve this pain point. The following is a case of selling steamed stuffed buns to optimize the code.

/**
 * Steamed stuffed bun shop
 */
public class BaoziShop2 {
    /**
     * steamed stuffed bun
     */
    static class Baozi{
        private int id;
        public Baozi(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "steamed stuffed bun--" + id;
        }
    }


    public static void main(String[] args) {
        //Blocking queue
        BlockingQueue<Baozi> baozis = new ArrayBlockingQueue<>(100);
        //Producer thread
        new Thread(() -> {
            for (int i = 0; i < 200; i++) {
                //Create a package and add it to the blocking queue. When it is full, it will automatically block the thread
                Baozi baozi = new Baozi(baozis.size() + 1);
                try {
                    baozis.put(baozi);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"Produced"+baozi);
            }
        }).start();
        //Consumer thread
        for(int i = 0;i < 5;i++){
            new Thread(() -> {
                //Take the steamed stuffed bun. If it is empty, it will block automatically
                for (int j = 0; j < 40; j++) {
                    try {
                        Baozi baozi = baozis.take();
                        System.out.println(Thread.currentThread().getName()+"Consumption"+baozi);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

Chapter 4 thread pool

Section 01 concept introduction

Why do we use the concept of thread pool? Because thread is a valuable system resource. In the previous traditional way, multithreading is implemented. When our thread executes all its instructions, its life cycle will be in a dead state. When we need to execute the task again, we need to create a new thread again. If a large number of tasks need to be processed and threads need to be created and destroyed frequently, it is a great waste of our system resources. Therefore, there is a thread pool to help us solve this waste.

The core idea of thread pool:

A certain number of threads will be saved in the thread pool. After executing a task, the thread will return to the thread pool and wait for the next task, saving system resources and improving performance. Simply put, the thread task will not enter the dead state after execution, but will enter the thread pool.

Section 02 use of thread pool

Top level interface: Executor

  • execute(Runnable) starts the thread to execute a task

ExecutorService

Inherit Executor

Add thread pool management methods, such as shutdown(), shutdown now()

Executors

Tool class for creating thread pool

Main methods

Method nameexplain
newCachedThreadPool()Create an unlimited thread pool
newFixedThreadPool(int )Create a fixed length thread pool
newSingleThreadExecutor()Create a single number of thread pools
newScheduledThreadPool(int)Create a thread pool that can be scheduled

Section 03 case codes

import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo {

    public static void useCachedThreadPool(){
        //Create an unlimited thread pool
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            int n = i;
            //Start threads using thread pool
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"Performed the task" + n);
            });
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
    }

    public static void useFixedThreadPool(){
        //Create a fixed length thread pool
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 100; i++) {
            int n = i;
            //Start threads using thread pool
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"Performed the task"+n);
            });
        }
        executorService.shutdown();
    }

    public static void useSingleThreadPool(){
        //Create a single length thread pool
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 100; i++) {
            int n = i;
            //Start threads using thread pool
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"Performed the task"+n);
            });
        }
        executorService.shutdown();
    }

    public static void useScheduledThreadPool(){
        //Get a schedulable thread pool
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        //Execute schedulable tasks
        System.out.println("------------");
        scheduledExecutorService.scheduleAtFixedRate(()->{
            System.out.println(Thread.currentThread().getName()+"---->"+ LocalDateTime.now());
        },1,3, TimeUnit.SECONDS);
    }

    public static void main(String[] args) {
//        useCachedThreadPool();
//        useFixedThreadPool();
//        useSingleThreadPool();
        useScheduledThreadPool();
    }
}

Chapter V optimized configuration of thread pool

Section 01 introduction

Implementation class of thread pool

ThreadPoolExecutor

Construction method parameters of thread pool:

  • corePoolSize is the number of core threads. After creating a thread pool, the threads are brought with it and will not be destroyed
  • maximumPoolSize maximum number of threads
  • keepAliveTime: the time that non core threads can be idle. After that, they are destroyed
  • timeUnit time unit
  • blockingQueue blocks the collection of queued Runnable tasks

Executors tool class part of the source code

Tips:

Through the source code, we will find a problem. In fact, the Executors tool class used to create thread pools above actually helps us initialize the construction methods of different ThreadPoolExecutor classes internally, and achieve the functions of different thread pools by matching the values of construction method parameters in different proportions.

Optimized configuration

  1. The number of core threads should be related to the number of CPU cores * N (N is related to the time and concurrency required for task execution)

    // View the code for the number of CPU cores
    Runtime.getRuntime().availableProcessors()
    
  2. The maximum number of threads can be the same as the number of core threads to avoid frequent creation and destruction of threads

  3. If there are non core threads, set it larger to avoid frequent creation and destruction of threads

  4. The blocking queue uses LinkedBlockingQueue, which is more efficient for inserting and deleting tasks

The sixth chapter is the implementation principle of thread pool

After learning for so long, we really want to know how our thread pool is implemented, and why when a thread executes all instructions, it does not enter the life cycle of death. Let's analyze it through the source code.

Why our ThreadPoolExecutor class can call the execute method can be understood through the following inheritance implementation relationship. In fact, our ThreadPoolExecutor class is finally related to the Executor interface.



Let's enter the source code to see what happens internally when the execute method executes the thread pool.

We can see here that whenever we execute the thread pool, we first judge whether a task implementing the Runnable interface is passed in. If not, a null pointer exception will be thrown. Then it will compare the number of core threads with the number of non core threads. If the number of non core threads is less than the number of core threads, the core thread will be created. Instead, create a non core thread. Then execute the addWorker () method to create a worker object and store it in the hashSet. Then execute the runWorker method to execute the thread.

Then, the follow-up will call the getTask method repeatedly. If the task queue is empty, the thread will block. Until there is a new task in the blocking queue.

In fact, in short. It is a thread set workerSet and a blocking queue workQueue. When a user submits a task to the thread pool, the thread pool will first put the task into the workQueue. The threads in the workerSet will continuously obtain threads from the workQueue and then execute. When there are no tasks in the workQueue, the worker will block until there are tasks in the queue, and then take them out to continue execution.

Today's summary

  1. Thread waiting and notification (what to do and who to call)
    Thread waiting is called through the lock object, which is used to make the current thread enter the waiting state until it is notified.
  2. The difference between wait and sleep
  • 1. Different calling objects

    wait() is called by the lock object

    sleep() is called by a thread

  • 2. Different locks are used

    After the wait is executed, the lock is automatically released

    After sleep is executed, the lock is not released

  • 3. Different wake-up mechanisms

    After executing wait, you can be notified to wake up

    After executing sleep, you can only wait for the time to end and wake up automatically

  1. Producer and consumer model (what is it, what problems are solved, how to implement it, and what applications are there in the project)
    This is a design pattern that does not belong to GOF23. Some processes / threads are responsible for generating / consuming data. Implemented by buffer.

  2. What is a thread pool
    A certain number of threads will be saved in the thread pool. After executing a task, the thread will return to the thread pool and wait for the next task, saving system resources and improving performance. Simply put, the thread task will not enter the dead state after execution, but will enter the thread pool.

Topics: Java Back-end