Hashtag: “We are all little frogs” public account article

Eyes from the toilet go to a restaurant, a restaurant usually have many cooks and many waiter, here we call chef producers, called the waiter consumers, chefs and waiters are not directly deal with, but after cook good food on the window, the waiter directly put the dishes away from the window to the guest, this will greatly improve work efficiency, Because it saves the communication cost between producer and consumer. From Java’s point of view of this, every cook is equivalent to a producer threads, each attendant is equivalent to a consumer threads, and put the window of the dish is equivalent to a buffer queue, the producer thread ever put producing good things in the buffer queue, consumer threads continuously take something from the buffer queue, draw a diagram like this:

In reality, the window for putting dishes is limited in number. Let’s assume that the window can only put 5 dishes. Then the chef needs to see if the window is full after finishing the dishes. If the window is full, he will smoke a cigarette and wait until the waiter comes to pick up the dishes and informs the chef that the window is free for dishes. At this time, the chef will put his dishes in the window to fry the next dish. From the waiter’s point of view, if the window is empty, go and smoke a cigarette and wait until the chef has prepared the dish and put it on the window, and inform them, and then take the dish away.

Let’s start with a Java abstraction:

public class Food {
    private static int counter = 0;

    private int i;  // The number of dishes produced

    public Food(a) {
        i = ++counter;
    }

    @Override
    public String toString(a) {
        return "The first" + i + "A dish"; }}Copy the code

Each time the Food object is created, the value of field I is incremented by 1 to indicate the number of dishes created.

To get the story going, let’s first define a utility class:

class SleepUtil {

    private static Random random = new Random();
    
    public static void randomSleep(a) {
        try {
            Thread.sleep(random.nextInt(1000));
        } catch (InterruptedException e) {
            throw newRuntimeException(e); }}}Copy the code

The static method of SleepUtil randomSleep represents the time within one second of the current thread’s randomSleep.

Let’s define the chef in Java:

public class Cook extends Thread {

    private Queue<Food> queue;

    public Cook(Queue<Food> queue, String name) {
        super(name);
        this.queue = queue;
    }

    @Override
    public void run(a) {
        while (true) {
            SleepUtil.randomSleep();    // Simulate cook cooking time
            Food food = new Food();
            System.out.println(getName() + "Produced." + food);
            synchronized (queue) {
                while (queue.size() > 4) {
                    try {
                        System.out.println("If there are more than 5 queue elements:" + queue.size() + "" + getName() + "Have a cigarette and wait.");
                        queue.wait();
                    } catch (InterruptedException e) {
                        throw newRuntimeException(e); } } queue.add(food); queue.notifyAll(); }}}}Copy the code

We say that every Cook is a thread that maintains an internal queue called queue. In the run method, there is an infinite loop, which represents continuous production of Food. If the number of Food items in the queue is greater than 4, queue.wait() is called. If the number of Food items in the queue is less than 4, queue.wait() is called. So this code is locked with a queue object. When the newly created Food object has been added to the queue, the queue can be notified that the wait queue attendant threads associated with the lock object can continue serving.

Let’s use Java to define the server:

class Waiter extends Thread {

    private Queue<Food> queue;

    public Waiter(Queue<Food> queue, String name) {
        super(name);
        this.queue = queue;
    }

    @Override
    public void run(a) {
        while (true) {
            Food food;
            synchronized (queue) {
                while (queue.size() < 1) {
                    try {
                        System.out.println("Number of queue elements is:" + queue.size() + "," + getName() + "Have a cigarette and wait.");
                        queue.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                food = queue.remove();
                System.out.println(getName() + "Obtain:" + food);
                queue.notifyAll();
            }

            SleepUtil.randomSleep();    // Simulate the server serving time}}}Copy the code

Each server is a thread, and like the cook, maintains an internal queue called queue. In the run method, there is an infinite loop, which represents the continuous removal of Food from the queue. Each time we fetch a Food object from a queue, we need to check whether the element in the queue is less than 1. If it is less than 1, we call queue.wait(). If it is not less than 1, we remove a Food object from the queue. Notify the chef threads in the queue associated with the lock object that they can continue to add Food objects to the queue.

With the chef and waiter thread classes defined, let’s create a Restaurant class to see what actually happens in a Restaurant:

public class Restaurant {

    public static void main(String[] args) {

        Queue<Food> queue = new LinkedList<>();
        new Cook(queue, "Chef 1").start();
        new Cook(queue, "Chef 2").start();
        new Cook(queue, "Chef 3.").start();
        new Waiter(queue, "Waiter Number one").start();
        new Waiter(queue, "Waiter 2").start();
        new Waiter(queue, "Waiter Number three").start(); }}Copy the code

We have arranged three cooks and three waiters in the Restaurant. If we execute this program, we will find that if the chef produces too fast, the chef will wait, and if the waiter delivers too fast, the waiter will wait. But the cook and the waiter have nothing to do with each other, they are decoupled through queues.

This process is not very complicated, but there are still some problems to pay attention to in use:

  • Our cooks and waiters use the same lock queue.

    The same lock is used because operations on queues can only be protected by the same lock. Suppose that using different locks, the cook thread calls queue.add and the waiter thread calls queue.remove. These two methods are not atomic operations, and the simultaneous execution of multiple threads will have unpredictable results. So we use the same lock to protect the queue variable, which we emphasized when we talked about designing thread-safe classes.

  • The consequence of using the same lock queue is that the cook thread and the waiter thread use the same wait queue.

    But at the same time the chef thread and waiters thread won’t be waiting in the queue at the same time, because when the chef thread at the time of wait, the element in the queue is 5, the waiter thread is certainly not wait, but the process of consumption is locked object queue protection, so in a waiter thread after a Food consumption, NotifyAll is called to wake up the cook threads in the wait queue; When the consumer thread is waiting, the element in the queue must be 0, and the cook thread must not wait. The production process is protected by the lock queue. Therefore, after a cook thread produces a Food object, a notifyAll is called to wake up the waiter threads in the wait queue. So the cook thread and the server thread are not in the waiting queue at the same time.

  • We called sleeputil.randomsleep () during both production and consumption; .

    Our producer-consumer model is a simplification of the actual scenario in which production and consumption processes are time-consuming and it is best not to put these time-consuming operations in synchronized code blocks, which would cause long blocks in other threads. If you put both the production and consumption processes in a synchronized code block, that is to say, one chef can’t cook at the same time, one waiter can’t serve at the same time, this is obviously not reasonable, people need to pay attention to this.

This is an introduction to a real-world application of Wait/Notify: the producer-consumer model.

digression

Writing articles is tiring, and sometimes you feel that the reading is smooth, but it is actually the result of countless revisions behind. If you feel good please help to forward, thank you ~ here is my public number, there are more technical dry goods, from time to time pull a calf, welcome to pay attention to:

Small volumes

In addition, the author also wrote a short book on MySQL: How MySQL works: A link to understand MySQL from the root. The content of the volume is mainly from the perspective of small white, using popular language to explain some core concepts about MySQL advanced, such as record, index, page, table space, query optimization, transaction and lock, a total of about 300,000 or 400,000 words, with hundreds of original illustrations. MySQL > MySQL > MySQL > MySQL > MySQL > MySQL > MySQL > MySQL > MySQL