day27_ Thread communication and thread status

Keywords: Java

Inter thread communication

Why handle inter thread communication

Multiple threads are processing the same resource, but the processing actions (tasks of threads) are different. When multiple threads execute concurrently, by default, the CPU switches threads randomly. When we need multiple threads to complete A task together, and we want them to execute regularly, some communication mechanisms are needed between multiple threads to coordinate their work, so as to help us achieve multi threads to jointly operate A piece of data. For example, thread A is used to generate buns and thread B is used to eat buns. Buns can be understood as the same resource. Thread A and thread B process actions, one is production and the other is consumption. At this time, thread B must wait until thread A completes, so thread communication is required between thread A and thread B, that is, wait for wake-up mechanism.

What is the waiting wake-up mechanism

This is a collaboration mechanism between multiple threads. When it comes to threads, we often think of the race between threads, such as competing for locks, but this is not the whole story. There will also be a cooperation mechanism between threads. When a thread meets a certain condition, it enters the wait state (wait()/wait(time)), waits for other threads to wake up after executing their specified code (notify()); Or you can specify the time of wait and wake up automatically when the time comes; When there are multiple threads waiting, you can use notifyAll() to wake up all waiting threads if necessary. wait/notify is a cooperative mechanism between threads.

Wait and wake methods of Object class

Details of calling wait and notify methods

  • The wait method and notify method must be called by the same lock object. Because: the corresponding lock object can wake up the thread after using the wait method called by the same lock object through notify.
  • The wait method and notify method are methods belonging to the Object class. Because: the lock Object can be any Object, and the class of any Object inherits the Object class.
  • The wait method and notify method must be used in the synchronization code block or synchronization function. Because: these two methods must be called through the lock object.

Classic case: producer and consumer issues

Resource class

package demo03;

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
    private boolean flag;

    //Total number of hamburgers
    //In the future, we will use this variable that must have a default value
    private int count;

    //Lock 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 +
                '}';
    }
}

Chef class

package demo03;

public class Cooker extends Thread {
    private Desk desk;

    public Cooker(Desk desk) {
        this.desk = desk;
    }


    @Override
    public void run() {
        while (true) {
            synchronized (desk.getLock()) {
                if (desk.getCount() == 0) {
                    break;
                } else {
                    //Judge whether there are hamburgers on the table. If so, wait. If not, it will be produced.
                    if (!desk.isFlag()) {
                        //The producer put the hamburger on the table.
                        System.out.println("The cook is making hamburgers");
                        desk.setFlag(true);
                        //Wake up the waiting consumers and start eating.
                        desk.getLock().notifyAll();
                    } else {
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

Food category

package demo03;

public class Foodie extends Thread {
    private Desk desk;

    public Foodie(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (desk.getLock()) {
                if (desk.getCount() == 0) {
                    break;
                } else {
                    //  1. Judge whether there are hamburgers on the table.
                    if (desk.isFlag()) {
                        //If you have it, eat it
                        System.out.println("The food is eating hamburgers");
                        desk.setFlag(false);
                        //After eating, there is no hamburger on the table. The producers waiting to wake up continue to produce
                        desk.getLock().notifyAll();
                        // The total number of hamburgers is reduced by one
                        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();
                        }
                    }
                }
            }
        }

    }
}


Test class

package demo03;

public class Test {
    public static void main(String[] args) {
        Desk desk = new Desk();
        Cooker cooker = new Cooker(desk);
        Foodie foodie = new Foodie(desk);
        cooker.start();
        foodie.start();
    }
}

There are actually two problems implied in the problem of producers and consumers:

  • Thread safety problem: because producers and consumers share data buffers, but this problem can be solved using synchronization.
  • Thread coordination: to solve this problem, the producer thread must wait when the buffer is full, pause and enter the blocking state, notify the waiting thread to return to the ready state and restart adding data to the buffer the next time the consumer consumes the data in the buffer. Similarly, you can also let the consumer thread wait when the buffer is empty, pause and enter the blocking state, wait until the producer adds data to the buffer, and then notify the waiting thread to return to the ready state. Such problems can be solved through such a communication mechanism

The blocking queue completes communication between threads

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 is 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

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 queues enable communication between threads

public class Cooker extends Thread {

    private ArrayBlockingQueue<String> bd;

    public Cooker(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }

    @Override
    public void run() {
        while (true) {
            try {
                //Loop adds a packet to the blocking queue
                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() {
        while (true) {
            try {
                //Loop to get the packet in the blocking queue
                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) {
        //Create blocking queue object
        ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);

        Foodie f = new Foodie(bd);
        Cooker c = new Cooker(bd);

        f.start();
        c.start();
    }
}

Thread state

When a thread is created and started, it neither enters the execution State as soon as it is started, nor is it always in the execution State. Thread objects have different states in different periods. So what are the states of threads in Java? The thread State in Java is defined in the java.lang.Thread.State enumeration class. The source code of the State enumeration class is as follows:

public class Thread {
    
    public enum State {
    
        /* newly build */
        NEW , 

        /* Operational status */
        RUNNABLE , 

        /* Blocking state */
        BLOCKED , 

        /* Infinite waiting state */
        WAITING , 

        /* Timed waiting  */
        TIMED_WAITING , 

        /* termination */
        TERMINATED;
    
	}
    
    // Gets the status of the current thread
    public State getState() {
        return jdk.internal.misc.VM.toThreadState(threadStatus);
    }
    
}

Through the source code, we can see that there are six thread states in Java. The meaning of each thread state is as follows

The transition of each state is shown in the following figure:

Posted by suepahfly on Thu, 16 Sep 2021 18:46:47 -0700