wait and notify principles

Posted by mu-ziq on Tue, 08 Mar 2022 16:13:44 +0100

1.wait and notify

1.1 short story


1.2 principle

  • 1. The current thread must own the monitor (lock) of this object.
  • 2. When the current thread calls the wait () method, the thread will release the ownership of the lock and wait
  • 3. Until another thread notifies the thread waiting on the object's monitor (lock) to wake up by calling the notify method or notifyAll method.
  • 4. Then the thread waits until it can regain ownership of the monitor (lock) of the object, and then continues to execute (after being awakened, it needs to wait until it obtains the lock to continue to execute).

1.3 API introduction

obj.wait() and obj notify()

  • obj.wait() causes the thread entering the object monitor to wait in the waitSet
  • wait(long n): when the waiting thread is not notified, it will wake up automatically after the waiting time
  • obj.notify() selects one of the threads waiting for waitSet on the object to wake up
  • obj.notifyAll() wakes up all the threads waiting for waitSet on the object

They are all means of cooperation between threads and belong to the methods of Object objects. You must obtain a lock on this Object to call these methods

wait()
Causes the current thread to wait until another thread calls the notify() method or notifyAll() method for this object. In other words, this method behaves as if it were only executing wait (0) (if there is no current object, notify () will wake up and wait all the time).

 synchronized (obj) {
		// while when the thread is awakened, judge whether the condition is met
          while (<condition does not hold Conditions not met>) 
              obj.wait();
          ... // Action of current behavior
      }
}

Code example

/**
 * @ClassName WaitNotifyTest
 * @author: shouanzh
 * @Description WaitNotifyTest
 * @date 2022/3/8 21:13
 */
@Slf4j
public class WaitNotifyTest {

    static final Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            synchronized (obj) {
                log.debug("t1 implement...");
                try {
                    // wait/notify can only be called after the lock object is obtained
                    obj.wait(); // At this time, t1 thread enters WaitSet and waits
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("t1 Other codes...");
            }
        }, "t1").start();


        new Thread(() -> {
            synchronized (obj) {
                log.debug("t2 implement...");
                try {
                    obj.wait(); // At this time, t2 thread enters WaitSet and waits
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("t2 Other codes...");
            }
        }, "t2").start();

        // Let the main thread wait for two seconds to execute. In order to wake up, if you don't sleep, the main thread starts to wake up before the two threads enter the waitSet
        Thread.sleep(1000);
        log.debug("awaken waitSet Threads in!");
        // wait/notify can only be called after the lock object is obtained
        synchronized (obj) {
            // obj.notify(); //  Wake up a thread in waitset
            obj.notifyAll(); // Wake up all waiting threads in waitset
        }
    }
}

obj.notifyAll();

obj.notify();

The difference between sleep(long n) and wait(long n)

  • sleep is the Thread method, while wait is the Object method
  • sleep does not need to be used with synchronized forcibly, but wait needs to be used with synchronized
  • sleep will not release the object lock while sleeping, but wait will release the object lock while waiting.

2. Correct use of wait / notify

Step 1
Analyze the following code:

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // Is there any smoke
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        Thread.sleep(2000);   // It will block for 2s and will not release the lock
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working");
                }
            }
        }, "Xiaonan").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("You can start working");
                }
            }, "Other people").start();
        }

        Thread.sleep(1000);
        new Thread(() -> {
            // There is no lock at this time, so it will be executed first before others
            // Can I add synchronized (room) here?
            //synchronized (room) {/ / if the lock is added, the cigarette delivery person also needs to wait for Xiaonan to sleep for 2s. At this time, even if it is delivered, Xiaonan thread will release the lock
                hasCigarette = true;
                log.debug("Here comes the smoke!");
            //}
        }, "Cigarette delivery").start();
    }
}
2022-03-08 22:05:28 [Xiaonan] - Any smoke?[false]
2022-03-08 22:05:28 [Xiaonan] - No smoke, take a break!
2022-03-08 22:05:29 [Cigarette delivery] - Here comes the smoke!
2022-03-08 22:05:30 [Xiaonan] - Any smoke?[true]
2022-03-08 22:05:30 [Xiaonan] - You can start working
2022-03-08 22:05:30 [Other people] - You can start working
2022-03-08 22:05:30 [Other people] - You can start working
2022-03-08 22:05:30 [Other people] - You can start working
2022-03-08 22:05:30 [Other people] - You can start working
2022-03-08 22:05:30 [Other people] - You can start working

Process finished with exit code 0


Step 2: wait notify mechanism

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // Is there any smoke
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        room.wait(); // The lock will be released without affecting the operation of other threads
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working");
                }
            }
        }, "Xiaonan").start();

        // Other threads
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("You can start working");
                }
            }, "Other people").start();
        }

        Thread.sleep(1000);

        // Cigarette delivery thread
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("Here comes the smoke!");
                room.notify();
            }
        }, "Cigarette delivery").start();
    }
}
2022-03-08 22:17:11 [Xiaonan] - Any smoke?[false]
2022-03-08 22:17:11 [Xiaonan] - No smoke, take a break!
2022-03-08 22:17:11 [Other people] - You can start working
2022-03-08 22:17:11 [Other people] - You can start working
2022-03-08 22:17:11 [Other people] - You can start working
2022-03-08 22:17:11 [Other people] - You can start working
2022-03-08 22:17:11 [Other people] - You can start working
2022-03-08 22:17:12 [Cigarette delivery] - Here comes the smoke!
2022-03-08 22:17:12 [Xiaonan] - Any smoke?[true]
2022-03-08 22:17:12 [Xiaonan] - You can start working

Process finished with exit code 0

It solves the problem of thread blocking of other working threads
But what if there are other threads waiting for conditions?

Step3

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // Is there any smoke
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

		// spurious wakeup 
        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        room.wait(); // At this point, you enter the waitset waiting collection and release the lock at the same time
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working");
                } else {
                    log.debug("No dry survival...");
                }
            }
        }, "Xiaonan").start();


        new Thread(() -> {
            synchronized (room) {
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("No takeout, take a break!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("You can start working");
                } else {
                    log.debug("No dry survival...");
                }
            }
        }, "my daughter").start();

        Thread.sleep(1000);

        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("Here's the takeout!");
                room.notify();
            }
        }, "Delivery").start();

    }
}


Problem: when the takeout is delivered, it wakes up Xiaonan. At this time, there is a problem

notify can only wake up one thread in the WaitSet randomly. If other threads are waiting at this time, it may not wake up the correct thread, which is called false wake-up
The solution is to change to notifyAll

Step4: notifyAll

 		new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("Here's the takeout!");
                room.notifyAll();
            }
        }, "Delivery").start();
2022-03-08 22:33:29 [Xiaonan] - Any smoke?[false]
2022-03-08 22:33:29 [Xiaonan] - No smoke, take a break!
2022-03-08 22:33:29 [my daughter] - Did you deliver the takeout?[false]
2022-03-08 22:33:29 [my daughter] - No takeout, take a break!
2022-03-08 22:33:30 [Delivery] - Here's the takeout!
2022-03-08 22:33:30 [my daughter] - Did you deliver the takeout?[true]
2022-03-08 22:33:30 [my daughter] - You can start working
2022-03-08 22:33:30 [Xiaonan] - Any smoke?[false]
2022-03-08 22:33:30 [Xiaonan] - No dry survival...

Process finished with exit code 0

Xiao Nan was awakened. Xiao Nan went back to see if the delivery was takeout or cigarettes It's troublesome. How to solve it?

notifyAll only solves the wake-up problem of a thread, but there is only one chance to judge with if+ wait. Once the condition is not tenable, there is no chance to judge again

The solution is to use while + wait. When the condition is not tenable, wait again

Step5: use the while loop to solve the false wake-up

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // Is there any smoke
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        room.wait(); // At this point, you enter the waitset waiting collection and release the lock at the same time
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working");
                } else {
                    log.debug("No dry survival...");
                }
            }
        }, "Xiaonan").start();


        new Thread(() -> {
            synchronized (room) {
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("No takeout, take a break!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("You can start working");
                } else {
                    log.debug("No dry survival...");
                }
            }
        }, "my daughter").start();

        Thread.sleep(1000);

        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("Here's the takeout!");
                room.notifyAll();
            }
        }, "Delivery").start();

    }
}

Code for wait() operation

synchronized (lock) {

    while (The condition is not tenable) {
       lock.wait();
    }
    // work
}

// Another thread
synchronized (lock) {
   // notifyAll() wake up
    lock.notifyAll();
}

3. Summary of wait, notify and notifyAll methods

  • When calling wait, you first need to ensure that the thread of the wait method has held the lock of the object (monitor).
  • When wait is called, the object will release the lock of the object and enter the wait set
  • When a thread enters the waiting state after calling wait, it can wait for other threads to call the notify or notofyAll method of the same object to wake itself up
  • Once the thread is awakened by other threads of the object, the thread will compete with other threads for the lock of the object (fair competition); Only after the thread obtains the lock of this object will the thread continue to execute
  • The code fragment calling the wait method needs to be placed in a synchronized block or synchronized method, so as to ensure that the thread has obtained the lock of the object before calling the wait method
  • When the notify method of an object is called, it will randomly wake up any thread in the object wait set. When a thread is awakened, it will compete with other threads for the lock of the object.
  • When the notifyAll method of an object is called, it will wake up all threads in the object wait set. After these threads are awakened, they will start competing for the lock of the object
  • At some point, only one thread can have a lock on an object.

Topics: Java