1. Functions and characteristics of Wait and notify

1.1 Both are methods in Object class and native methods

Both are methods in the Object class, so all objects in Java can call them

public final native void notify(a);
public final native void wait(long timeout) throws InterruptedException;
Copy the code

And from Java source, we can know: wait() notify() are native methods

1.2 A wait must be called in a try catch

** Wait () must be surrounded by a try catch, and notify() does not require **.

Why wait() requires a try catc:

  • From the source code above, we can see that the wait() throws aInterruptedExceptionException, so we must handle this exception when we use wait()

  • No exceptions are thrown in notify(), so we don’t need to process it

1.3 Whether to Release the Lock

  1. callWhen wait(), the lock is released, but only the locks that called wait() are released, not the other locks
  2. When notify() is called, the lock is not released

Next, let’s use code to verify that the lock is released

1.Verify with wait()public void run(a) {
        super.run();
      	// condition is locked
        synchronized (condition){
            try {
                System.out.println("Entering the critical zone");
                condition.wait();
              	// Because the lock is released, the following statement is not printed
                System.out.println("Wait () called, no output");
            }catch(Exception e){ e.printStackTrace(); }}}2.Verify with notify()public void run(a) {
        super.run();
      	// condition is locked
        synchronized (condition){
                System.out.println("Entering the critical zone");
                condition.notify();
              	// Because the lock is released, the following statement is not printed
                System.out.println("If I call notify(), this is still going to print out."); }}Copy the code

If we run the above code separately, we can see that wait() does not print, and notify() still prints, indicating that wait() releases the lock and notify() does not release the lock

We know that notify() does not release the lock

  • Notify () is best placed at the end of critical section code whenever possible

    Because notify() does not release the lock, if we call notify in the middle or in front of the code in the critical section, we wake up another thread, and notify does not release the lock. As a result, that thread may not acquire the lock for a period of time, resulting in context switching and poor performance

1.4 Producer and Consumer

Wait and notify are commonly used among consumer producers, which fall into four categories

  • Single producer single consumer
  • Many producers and single consumers
  • One producer, many consumers
  • More producers, more consumers

However, the use of wait and notify is similar in all four modes. For example, note the following:

  • Wait and notify store things in one place, usually using blocking queues such as ArrayList, LinkedList, etc

  • Notify is placed at the end of the consuming thread

  • A wait call needs to be in a loop, and the loop needs to be in a critical section

    The purpose of the loop is to ensure that the blocking queue does not exceed its maximum capacity in the case of multiple producers

    The purpose of critical sections is to ensure thread-safe changes to judgment conditions in loops

    The code for wait is as follows:

    // Make sure that the modification of the judgment condition is atomic
    atomic{
      while{call wait()} execute production operation}// Synchronized
    synchorized(res){
      // Res are shared by producers and consumers
      while{res.wait(); } Perform production operations}Copy the code

2. Precautions when using Wait () and notify()

2.1 Wait () in a loop

Wait () is usually placed in a loop, and the criteria for the loop determine whether to call wait().

We can use while and for, but generally we use a while loop

This is shown in the 1.4 code

Loops are usually placed at the front of a critical section

We know that the most common scenarios for wait() and notify() are in the producer-consumer mode, and that wait() is generally used in the consumer thread when the judgment condition is true (e.g. More stuff is produced than can be stored, and consumers can’t consume as fast as they can), we pause the production thread, but when we wake it up, it continues to wait()

Furthermore, the purpose of using wait() is to control the speed at which the production thread can produce code, and if the code is not produced after wait(), it cannot actually control the speed

Why not use if instead of a loop

Because if we use if, in the multi-producer scenario, we get an error

For example, something like this might happen:

There are three producers, ABC, that produce things in a bounded queue

A the runtime queue is full and pauses with wait()

Then at some point, the consuming thread wakes up all the producing threads, but A doesn’t grab the lock. When A grabs the lock, BC fills the queue again, but our wait() is in if, and we should continue to call wait() because the queue is full, but because if can only be used once, Therefore, A can only continue to execute the production code, resulting in exceptions, such as the code below

// Consumers and producers access things in an ArrayList
// ArrayList can store a maximum of three, and if more than three, an exception will be thrown
class Producer extends Thread {
    ArrayList<Integer> list ;
    public Producer (ArrayList<Integer> list){
        this.list = list;
    }
    @Override
    public void run(a) {
        super.run();
        while (true) {synchronized (list){
                try {
                  // Use if here
                  // There may be threads that do not wake up, size is still equal to 3
                    if (list.size() >= 3){
                        list.wait();
                    }
                    list.add(1);
                    System.out.println("Produced one.");
                  	// If more than three are stored, an exception is thrown
                    if (list.size() > 3) throw new Exception();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}
class Consumer extends Thread{
    ArrayList<Integer> list ;
    public Consumer (ArrayList<Integer> list){
        this.list = list;
    }
    @Override
    public void run(a) {
        while (true) {synchronized (list){
                     list.remove(0);
                     System.out.println("Consumed one."); list.notifyAll(); }}}}Copy the code

When we run the above code in a multi-producer situation, we will find that exceptions are sometimes thrown, and we change if to ‘while’ so no exceptions are reported

In the case of a single producer, neither if nor while throws an exception, but we cannot guarantee whether the class will be used as a single producer or as a multi-producer

so

  • In the case of multiple producers, always use loops
  • In the case of a single producer, ‘if’ can be used, but since there is no guarantee that the class will be a single producer when used, a loop is recommended

2.2 Ensure thread-safe judgment conditions

Because the judgment condition may be modified in the case of multithreading, such as the case of multiple producers or consumers, and in the case of the judgment condition is modified, thread unsafe situation may occur

In general, we just want to make sure that the modification of the judgment condition is atomic

However, for convenience, we usually put the loop where the judgment condition is located in the critical region

If you don’t guarantee thread safety, IllegalMonitorStateException abnormalities may occur

This is usually the case when there is no lock (i.e., no synchonized(RES)) on the RES, which are resources shared by consumers and producers

Here is the code for a normal consumer thread

class Produce extends Thread{
    private Integer res ;
    private boolean condition = true;
    public Produce(Integer res){
        this.res = res;
    }
    @Override
    public void run(a) {
        super.run();
        synchronized (res){
            try{
                while (condition){
                    res.wait();
                }
                System.out.println("Production");
            }catch(Exception e){ e.printStackTrace(); }}}}Copy the code

But if we delete synchronized (res), we get an error

So, if a IllegalMonitorStateException abnormalities, check whether the thread safety is guaranteed

2.3 Positions of notify() and wait() in critical sections

  1. Notify () is placed at the end of the critical section code

    Because notify() does not release the lock, it may cause a context switch if it is placed first after waking up other threads because the lock corresponding to notify() is not released

    So in order for other threads to get the lock as quickly as possible without sending a context switch, we put it as late as possible

  2. The loop in wait() is usually placed at the front of the critical section

    Because the purpose of wait() is to control the speed at which the producer thread produces

    If wait() does not precede the production code, then speed control cannot be achieved. (Wait () cannot affect the production code because the production code executes before the wait() is called.)

2.4 Use Notify or notifyAll

Note whether to use notify or notifyAll

  • Notify Wakes up a thread randomly
  • A notifyAll wakes up all threads, but a notifyAll can causePremature wake up, context switchThe problem of

Because there are some problems with notifyALl(), do not use notifyALl() if we can do it with notify()

In general, we can use notify instead of notifyAll if the following two conditions are met

  • At most one thread needs to be woken up at a time

  • All wait threads are the same thread

    This can be interpreted as:

    Multiple producers, if they all produce the same thing, then whichever thread wakes up will satisfy the needs of the consumer thread

    Multiple producers. Different producer threads produce different things, let’s say a, B, C. If the consumer thread needs a, but notify may randomly wake up B, C, so we need to use notifyAll().

2.5 Consider using Condition

Condition is a Java library that provides an alternative to wait notify. It solves the problem of premature awakening and the inability of wait() to distinguish whether its return is due to timeout

If wait notify does not satisfy your needs, consider using Condition

3. Some problems with notify and Wait

3.1 Premature awakening

This problem often occurs when you have multiple criteria that depend on an object’s wait and notify

When notifyAll() is used by the consumer thread to wake up the production thread, condition A is satisfied, but condition B is not, so the thread of condition B is woken up early

We can use Condition to solve this problem

We can solve this problem by using Condition. With Condition, we can set one Condition for each judgment Condition, and call signal() only affecting the corresponding thread, thus solving the problem of premature wake up

But note: Condition only addresses premature awakening, not signal loss and context switching

3.2 Signal Loss

There are two cases of this problem

  1. Notify () called at an inappropriate time

    If this is possible:

    We have not called wait() yet, but we have called notify(), so when we need to wake up a thread, we cannot wake up because we have already called notify(), and the waiting thread does not receive the wake up signal from the wake up thread, so the signal is lost

    For this problem, we can nest a loop around notify()

  2. NotifyAll () is used when notify() should be used

    For example, we have two threads, A and B, and we want to wake up A, but notify() wakes up A random thread. If B wakes up, then the signal is lost to A

As you can see, signal loss is actually a code level problem, not a Java native problem, so we just need to be careful when we write the code

3.3 Deceptive awakening

As the name suggests, deceptive wake-up is waking up threads without notify() or notifyAll()

There are two common scenarios:

  1. A problem caused by non-Interruptedexception

    This is a problem that occurs in the JVM, but it occurs infrequently

    In this case, we simply place the judgment condition and wait() in a loop within the critical section

    The next loop will call wait(), even if it is spoofed, if the criteria are not met.

  2. InterruptedException causes a problem

    Interrupt () is called but not processed because while the thread is inSleep () or wait ()If we call Interrupt(), it throwsjava.lang.InterruptedExceptionException, when the JVM automatically wakes up the thread

    In this case, we simply handle InterruptedException in the catch

InterruptedException causes fraudulent wakeup, as shown in the following code

public class Main{
    public static void main(String[] args) {
            MyThread myThread = new MyThread(1);
            myThread.start();

            while (true) {try {
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("Main thread sleep completed"); myThread.interrupt(); }}}class MyThread extends Thread{
    Integer input;
    public MyThread(Integer input){
        this.input = input;
    }
    @Override
    public void run(a) {
        super.run();
        synchronized (input){
            while (true) {try {
                    System.out.println("Call wait() here");
                    input.wait();
                } catch (InterruptedException e) {
                  // InterruptException is not processed here, which causes problems
// e.printStackTrace();
                }
                System.out.println(I'm calling an interrupt here);
                System.out.println("There was no notify called, but the thread woke up.");
                System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); }}}}Copy the code

So what we find is: we didn’t call notify, but we found that there was no call, but the thread woke up, and every second there was an execution

3.4 Context Switch

Context switch problems are caused by multiple threads not being able to grab the lock in time

Context switching is a JVM-level problem that we can’t completely solve, but we can optimize it in two ways

  1. Do not use a notifyAll() when notify() can be done

  2. Notify () notifyAll() is placed at the end of the critical section

    Because notify() does not release the lock, we want the waiting thread to acquire the lock as soon as it wakes up, so we want notify() to release the lock as soon as it wakes up, in order to avoid a context switch

3.5 lock

A lock caused by WAIT and notify is a nested monitor lock, where multiple locks are nested and the internal lock cannot be awakened

Like the following code:

public class Main{
    public static void main(String[] args) {
        Integer outer = new Integer(1);
        Integer res = new Integer(0);

        Thread producer = new Producer(outer,res);
        producer.start();
        Thread consumer = new Consumer(outer,res);
        consumer.start();

        // Pause the main thread for three seconds, then check the state of the consumer and producer threads
        try {
            Thread.sleep(3000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("State of consumer thread:"+consumer.getState());
        System.out.println("State of the producer thread:"+producer.getState()); }}class Producer extends Thread{
    private Integer outer ;
    private Integer res;

    public Producer (Integer outer,Integer res){
        this.outer = outer;
        this.res = res;
    }
    @Override
    public void run (a){
        super.run();
        synchronized (outer){
            synchronized (res){
                try {
                  // Set it to true for convenience
                    while (true){
                        System.out.println("The producer thread calls wait()");
                        res.wait();
                    }
                    // Since condition is always true, it will not run here
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

class Consumer extends Thread{
    private Integer outer ;
    private Integer res;
    public Consumer (Integer outer,Integer res){
        this.outer = outer;
        this.res = res;
    }
    @Override
    public void run (a){
        System.out.println("Consumer thread running");
        synchronized (outer) {
            synchronized (res){
                // In this case, the consumer thread will not get the outer lock
                // Synchronized (outer) does not run
                System.out.println("The consumer thread got the outer lock.");
                System.out.println("Consumption"); res.notify(); }}}}Copy the code

The main content of the above code is as follows:

  • Both the consumer and producer have two variables, outer and res, and both are locked in run() andThe OUTER lock is required to obtain the RES lock
  • The production thread’s judgment condition is set to true, so the production thread calls wait() immediately to release the RES lock
  • The main thread starts the consumer and producer threads, sleeps for three seconds, and then looks at the state of both threads

The result is as follows

We found that:

  • The consumer thread does not perform a production operation after the producer thread calls wait()
  • Both threads end up blocked

After analysis, the reasons are as follows:

The cause of the current result is a lock, or rather a nested monitor lock

The reason for this is that wait() can only affect the lock on the object that called wait, and in this case we must obtain the external lock before we can continue to apply for the lock on the object of wait

The producer thread never released the external lock, causing the consumer thread to be unable to apply for the object lock of wait, resulting in a lock