Multithreading: producers and consumers

Posted by abhi201090 on Mon, 24 Jan 2022 03:18:39 +0100

catalogue

Producer and consumer model

Overview of producer and consumer models [application]

Producer and consumer case [application]

Producer and consumer case optimization [application]

Basic use of blocking queue [understanding]

Blocking queue realizes waiting wake-up mechanism [understanding]

Producer and consumer model

Overview of producer and consumer models [application]

  • summary

    Producer consumer model is a very classic multi-threaded cooperation model. Understanding the producer consumer problem can make us have a deeper understanding of multi-threaded programming.

    The so-called producer consumer problem actually includes two types of threads:

    One is the producer thread, which is used to produce data

    One is consumer thread, which is used to consume data

    In order to decouple the relationship between producers and consumers, shared data areas are usually used, just like a warehouse

    The producer's production data is directly placed in the shared data area, and does not need to care about the behavior of consumers

    Consumers only need to obtain data from the shared data area, and do not need to care about the behavior of producers

  • Wait and wake methods of Object class

    Method nameexplain
    void wait()Causes the current thread to wait until another thread calls the notify() method or notifyAll() method of the object
    void notify()Wake up a single thread waiting for the object monitor
    void notifyAll()Wake up all threads waiting for the object monitor

Producer and consumer case [application]

  • Case requirements

    • Table class (Desk): define variables representing the number of steamed stuffed buns, lock object variables, and variables marking whether there are steamed stuffed buns on the table

    • Producer class (Cooker): implement the Runnable interface, rewrite the run() method, and set the thread task

      1. Judge whether there are packages and decide whether the current thread executes

      2. If there are steamed stuffed buns, enter the waiting state. If there are no steamed stuffed buns, continue to execute and produce steamed stuffed buns

      3. After producing steamed stuffed buns, update the status of steamed stuffed buns on the table to awaken consumers to consume steamed stuffed buns

    • Consumer class (Foodie): implement the Runnable interface, rewrite the run() method, and set the thread task

      1. Judge whether there are packages and decide whether the current thread executes

      2. If there are no steamed stuffed buns, enter the waiting state. If there are steamed stuffed buns, consume steamed stuffed buns

      3. After consuming steamed stuffed buns, update the status of steamed stuffed buns on the table and wake up the producers to produce steamed stuffed buns

    • Test class (Demo): there is a main method. The code steps in the main method are as follows

      Create producer and consumer thread objects

      Open two threads respectively

  • code implementation

    /**
     * @author wangyy
     * @Date  2021-06-25
     */
    public class Desk {
    ​
        //Define a tag
        //true means that there are hamburgers on the table. At this time, it is allowed to eat goods
        //false means that there are no hamburgers on the table. At this time, the chef is allowed to execute
        public static boolean flag = false;
    ​
        //Total number of hamburgers
        public static int count = 10;
    ​
        //Lock object
        public static final Object lock = new Object();
    }
    ​
    public class Cooker extends Thread {
    //    Producer steps:
    //            1. Judge whether there are hamburgers on the table
    //    If there is, wait. If not, it will be produced.
    //            2. Put the hamburger on the table.
    //            3. Wake up the waiting consumers and start eating.
        @Override
        public void run() {
            while(true){
                synchronized (Desk.lock){
                    if(Desk.count == 0){
                        break;
                    }else{
                        if(!Desk.flag){
                            //production
                            System.out.println("The cook is making hamburgers");
                            Desk.flag = true;
                            Desk.lock.notifyAll();
                        }else{
                            try {
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
    ​
    public class Foodie extends Thread {
        @Override
        public void run() {
    //        1. Judge whether there are hamburgers on the table.
    //        2. If not, wait.
    //        3. Eat if you have one
    //        4. After eating, there are no hamburgers on the table
    //                Wake up the waiting producers and continue production
    //        The total number of hamburgers is reduced by one
    ​
            //tricks:
                //1. while(true) loop
                //2. For synchronized locks, the lock object should be unique
                //3. Judge whether the shared data is over end
                //4. Judge whether the shared data is over Not over
            while(true){
                synchronized (Desk.lock){
                    if(Desk.count == 0){
                        break;
                    }else{
                        if(Desk.flag){
                            //have
                            System.out.println("The food is eating hamburgers");
                            Desk.flag = false;
                            Desk.lock.notifyAll();
                            Desk.count--;
                        }else{
                            //No, just wait
                            //What object is used as a lock, you must use this object to call wait and wake methods
                            try {
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
    ​
        }
    }
    ​
    public class Demo {
        public static void main(String[] args) {
            /*Consumer steps:
            1,Judge whether there are hamburgers on the table.
            2,If not, wait.
            3,If you have it, eat it
            4,After eating, there are no hamburgers on the table
                    Wake up the waiting producers and continue production
            The total number of hamburgers is reduced by one*/
    ​
            /*Producer steps:
            1,Judge whether there are hamburgers on the table
            If there is, wait. If not, it will be produced.
            2,Put the hamburger on the table.
            3,Wake up the waiting consumers and start eating.*/
    ​
            Foodie f = new Foodie();
            Cooker c = new Cooker();
    ​
            f.start();
            c.start();
    ​
        }
    }

Producer and consumer case optimization [application]

  • demand

    • The variables in the Desk class are encapsulated in an object-oriented manner

    • The constructor in the producer and consumer classes receives the Desk class object and then uses it in the run method

    • Create producer and consumer thread objects, and pass in Desk class objects in the constructor

    • Open two threads

  • code implementation

    /**
     * @author wangyy
     * @Date  2021-06-25
     */
    public class Desk {
    ​
        //Define a tag
        //true means that there are hamburgers on the table. At this time, it is allowed to eat goods
        //false means that there are no hamburgers on the table. At this time, the chef is allowed to execute
        //public static boolean flag = false;
        private boolean flag;
    ​
        //Total number of hamburgers
        //public static int count = 10;
        //In the future, we will use such variables that must have default values
       // private int count = 10;
        private int count;
    ​
        //Lock object
        //public static final Object lock = new Object();
        private final Object lock = new Object();
    ​
        public Desk() {
            this(false,10); // Call the parameter inside the empty parameter to assign a value to the member variable, and then you can use the member variable directly
        }
    ​
        public Desk(boolean flag, int count) {
            this.flag = flag;
            this.count = count;
        }
    ​
        public boolean isFlag() {
            return flag;
        }
    ​
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    ​
        public int getCount() {
            return count;
        }
    ​
        public void setCount(int count) {
            this.count = count;
        }
    ​
        public Object getLock() {
            return lock;
        }
    ​
        @Override
        public String toString() {
            return "Desk{" +
                    "flag=" + flag +
                    ", count=" + count +
                    ", lock=" + lock +
                    '}';
        }
    }
    ​
    public class Cooker extends Thread {
    ​
        private Desk desk;
    ​
        public Cooker(Desk desk) {
            this.desk = desk;
        }
    //    Producer steps:
    //            1. Judge whether there are hamburgers on the table
    //    If there is, wait. If not, it will be produced.
    //            2. Put the hamburger on the table.
    //            3. Wake up the waiting consumers and start eating.
    ​
        @Override
        public void run() {
            while(true){
                synchronized (desk.getLock()){
                    if(desk.getCount() == 0){
                        break;
                    }else{
                        //System.out.println("verify whether it is executed");
                        if(!desk.isFlag()){
                            //production
                            System.out.println("The cook is making hamburgers");
                            desk.setFlag(true);
                            desk.getLock().notifyAll();
                        }else{
                            try {
                                desk.getLock().wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
    ​
    public class Foodie extends Thread {
        private Desk desk;
    ​
        public Foodie(Desk desk) {
            this.desk = desk;
        }
    ​
        @Override
        public void run() {
    //        1. Judge whether there are hamburgers on the table.
    //        2. If not, wait.
    //        3. Eat if you have one
    //        4. After eating, there are no hamburgers on the table
    //                Wake up the waiting producers and continue production
    //        The total number of hamburgers is reduced by one
    ​
            //tricks:
                //1. while(true) loop
                //2. For synchronized locks, the lock object should be unique
                //3. Judge whether the shared data is over end
                //4. Judge whether the shared data is over Not over
            while(true){
                synchronized (desk.getLock()){
                    if(desk.getCount() == 0){
                        break;
                    }else{
                        //System.out.println("verify whether it is executed");
                        if(desk.isFlag()){
                            //have
                            System.out.println("The food is eating hamburgers");
                            desk.setFlag(false);
                            desk.getLock().notifyAll();
                            desk.setCount(desk.getCount() - 1);
                        }else{
                            //No, just wait
                            //What object is used as a lock, you must use this object to call wait and wake methods
                            try {
                                desk.getLock().wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
    ​
        }
    }
    ​
    public class Demo {
        public static void main(String[] args) {
            /*Consumer steps:
            1,Judge whether there are hamburgers on the table.
            2,If not, wait.
            3,If you have it, eat it
            4,After eating, there are no hamburgers on the table
                    Wake up the waiting producers and continue production
            The total number of hamburgers is reduced by one*/
    ​
            /*Producer steps:
            1,Judge whether there are hamburgers on the table
            If there is, wait. If not, it will be produced.
            2,Put the hamburger on the table.
            3,Wake up the waiting consumers and start eating.*/
    ​
            Desk desk = new Desk();
    ​
            Foodie f = new Foodie(desk);
            Cooker c = new Cooker(desk);
    ​
            f.start();
            c.start();
    ​
        }
    }

Basic use of blocking queue [understanding]

  • Blocking queue inheritance structure

  • Common BlockingQueue:

    ArrayBlockingQueue: the bottom layer is an array, bounded

    LinkedBlockingQueue: the bottom layer is a linked list, unbounded But it's not really unbounded. The maximum is the maximum value of int

  • The core method of BlockingQueue:

    put(anObject): put the parameters into the queue. If they are not put in, they will be blocked

    take(): fetch the first data, otherwise it will be blocked

  • Code example

    /**
     * @author wangyy
     * @Date  2021-06-25
     */
    public class Demo02 {
        public static void main(String[] args) throws Exception {
            // Object to create a blocking queue with a capacity of 1
            ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
    ​
            // Storage element
            arrayBlockingQueue.put("hamburger");
    ​
            // Take element
            System.out.println(arrayBlockingQueue.take());
            System.out.println(arrayBlockingQueue.take()); // If you can't get it, it will block
    ​
            System.out.println("The program is over");
        }
    }

Blocking queue realizes waiting wake-up mechanism [understanding]

  • Case requirements

    • Producer class (Cooker): implement the Runnable interface, rewrite the run() method, and set the thread task

      1. Receive a blocking queue object in the construction method

      2. In the run method, loop to add packets to the blocking queue

      3. Print and add results

    • Consumer class (Foodie): implement the Runnable interface, rewrite the run() method, and set the thread task

      1. Receive a blocking queue object in the construction method

      2. Loop through the run method to get the packets in the blocking queue

      3. Print the obtained results

    • Test class (Demo): there is a main method. The code steps in the main method are as follows

      Create blocking queue object

      Create producer thread and consumer thread objects, and construct the incoming blocking queue object in the method

      Open two threads respectively

  • code implementation

  • /**
     * @author wangyy
     * @Date  2021-06-25
     */
    public class Cooker extends Thread {
    
        private ArrayBlockingQueue<String> bd;
    
        public Cooker(ArrayBlockingQueue<String> bd) {
            this.bd = bd;
        }
    //    Producer steps:
    //            1. Judge whether there are hamburgers on the table
    //    If there is, wait. If not, it will be produced.
    //            2. Put the hamburger on the table.
    //            3. Wake up the waiting consumers and start eating.
    
        @Override
        public void run() {
            while (true) {
                try {
                    bd.put("hamburger");
                    System.out.println("The cook put in a hamburger");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class Foodie extends Thread {
        private ArrayBlockingQueue<String> bd;
    
        public Foodie(ArrayBlockingQueue<String> bd) {
            this.bd = bd;
        }
    
        @Override
        public void run() {
    //        1. Judge whether there are hamburgers on the table.
    //        2. If not, wait.
    //        3. Eat if you have one
    //        4. After eating, there are no hamburgers on the table
    //                Wake up the waiting producers and continue production
    //        The total number of hamburgers is reduced by one
    
            //tricks:
            //1. while(true) loop
            //2. For synchronized locks, the lock object should be unique
            //3. Judge whether the shared data is over end
            //4. Judge whether the shared data is over Not over
            while (true) {
                try {
                    String take = bd.take();
                    System.out.println("Eat goods will" + take + "Take it out and eat it");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    
    public class Demo {
        public static void main(String[] args) {
            ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
    
            Foodie f = new Foodie(bd);
            Cooker c = new Cooker(bd);
    
            f.start();
            c.start();
        }
    }

Topics: Java Multithreading queue Concurrent Programming