Java multithreading series - thread communication mechanism wait notify notifyAll(03)

Thread communication mechanism wait notify notify all

In this class, we mainly learn the precautions for using the wait/notify/notifyAll method.

We mainly start with three questions:

  1. Why must the wait method be used in synchronized protected synchronization code?
  2. Why is wait/notify/notifyAll defined in the Object class and sleep defined in the Thread class?
  3. What are the similarities and differences between wait/notify and sleep methods?

wait must be used in synchronized protected synchronization code

Why can we use synchronization to protect the source code

/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
 * Make the current thread wait until another thread calls notify or notifyAll on this object
 * In other words, the behavior of this method is the same as calling wait(0)
 * <p>
 * The current thread must own this object's monitor. The thread
 * releases ownership of this monitor and waits until another thread
 * notifies threads waiting on this object's monitor to wake up
 * either through a call to the {@code notify} method or the
 * {@code notifyAll} method. The thread then waits until it can
 * re-obtain ownership of the monitor and resumes execution.
 * The current thread must be the monitor that holds this object. After calling the wait method, the thread will release the ownership of the monitor
 * Then wait until another thread wakes up the thread waiting in the monitor by executing notify or notifyAll
 * The awakened thread begins to wait until it can take ownership of the monitor and resume execution
 * <p>
 * As in the one argument version, interrupts and spurious wakeups are
 * possible, and this method should always be used in a loop:
 * <pre>
 *     synchronized (obj) {
 *         while (&lt;condition does not hold&gt;)
 *             obj.wait();
 *         ... // Perform action appropriate to condition
 *     }
 * </pre>
 * Like the fixed version, interruptions and false wakes are also possible, so this method is always used in a loop
 * This method should only be called by a thread that is the owner
 * of this object's monitor. See the {@code notify} method for a
 * description of the ways in which a thread can become the owner of
 * a monitor.
 * This method should be called by the thread holding the object monitor,
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of the object's monitor.
 * The current thread is not in the possession of the object monitor, so the IllegalMonitorStateException is thrown
 * @throws  InterruptedException if any thread interrupted the
 *             current thread before or while the current thread
 *             was waiting for a notification.  The <i>interrupted
 *             status</i> of the current thread is cleared when
 *             this exception is thrown.
 *If the current thread is interrupted by any thread while waiting, an InterruptedException is thrown
 */
public final void wait() throws InterruptedException {
    wait(0);
}

In fact, here we know why we should use it in synchronized, because the current thread must first hold the monitor of the object, that is, the lock, before it can call the wait method of the object, that is, it must have it first and then use it. And the calling form of this method is generally

synchronized (obj) {
     while (&lt;condition does not hold&gt;)
         obj.wait();
     ... // Perform action appropriate to condition
}

We call wait only when the conditions are met. In other words, we wait because the execution conditions are not met. Therefore, after waking up and obtaining the lock, we still need to judge whether to enter the wait. Because the execution conditions are not met when waking up, the first thing is to judge whether the conditions are met, We call this kind of wake-up as false wake-up, so even if it is false wake-up, we will check the conditions in while again. If the conditions are not met, we will continue to wait, which eliminates the risk of false wake-up.

Let's assume that if you don't need synchronized, let's see what happens,

public class WaitNotify {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
       buffer.add(data);
       // The notice can be consumed
       notify();
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty()) {
            // Wait empty
            wait();
        }
        return buffer.remove();
    }
}

We can see two methods in the code. The give method is responsible for adding data to the buffer. After adding, execute the notify method to wake up the waiting thread, while the take method is responsible for checking whether the entire buffer is empty. If it is empty, enter the waiting, and if not, take out a data. This is the idea of typical producers and consumers.

However, this code is not protected by synchronized, so the following scenarios may occur:

  1. First, the consumer thread calls the take method and determines the buffer Whether the isempty method returns true. If true, it means that the buffer is empty, the thread wants to enter the wait, but it is suspended by the scheduler before the thread calls the wait method, so there is no time to execute the wait method at this time.
  2. At this time, the producer starts running and executes the whole give method. It adds data to the buffer and executes the notify method, but notify has no effect. Because the wait method of the consumer thread has not been executed in time, no thread is waiting to be awakened.
  3. At this point, the consumer thread that has just been suspended by the scheduler returns to continue executing the wait method and enters the wait.

Although the consumer just judged the buffer Isempty condition, but when the wait method is actually executed, the previous buffer Isempty's result has expired and is no longer in line with the latest scenario, because the "judge execute" here is not an atomic operation. It is interrupted in the middle and is thread unsafe.

Assuming that there are no more producers to produce at this time, consumers may fall into endless waiting because they missed the wake-up of notify in the give method just now. We can see that it is precisely because the take method where the wait method is located is not protected by synchronized, so its while judgment and wait method cannot constitute atomic operation, Then the whole program is easy to make mistakes.

In fact, from here, we know why wait must be used with synchronized. And why is the fixed form used a while loop.

notify must be used in synchronized protected synchronization code

In fact, the reason is the same as wait, because only in this way can thread safety be guaranteed. The difference between notify and notifyAll is that notify only wakes up one thread waiting for the lock. NotifyAll fantasizes about all threads waiting for the lock, but only one thread can obtain the lock

The function of notify:

  1. Wake up a single thread that is specifying object wait. If there are multiple threads waiting on the object, select one of them to wake up.
  2. The choice is arbitrary and can be determined according to the implementation.
  3. The awakened thread will compete with any thread that needs to obtain the monitor (lock) of the specified object.
  4. The awakened thread has no advantage as the next thread to obtain the monitor (lock) of the specified object.

Why is wait/notify/notifyAll defined in the Object class and sleep defined in the Thread class

Why is the wait/notify/notifyAll method defined in the Object class? The sleep method is defined in the Thread class? There are two main reasons:

  1. Because each Object in Java has a lock called monitor monitor, because each Object can be locked, it requires a location in the Object header to store lock information. This lock is Object level, not thread level. wait/notify/notifyAll are also lock level operations. Their locks belong to objects, so it is most appropriate to define them in the Object class, because the Object class is the parent class of all objects, that is, all objects can become a lock Object, Therefore, when you look back at this time, synchronized should not be called lock. It is just a mechanism to ensure that only one Object can obtain the lock at the same time.
  2. Because if the wait/notify/notifyAll method is defined in the Thread class, it will bring great limitations. For example, a Thread may hold multiple locks in order to realize the complex logic of mutual cooperation. Assuming that the wait method is defined in the Thread class, how can a Thread hold multiple locks? How to specify which lock the Thread is waiting for? Since we are asking the current Thread to wait for the lock of an object, we should naturally implement it by operating the object, not the Thread. In fact, it's a bit like everyone competing for a public resource rather than the Thread itself holding the object, which may lead to some problems, such as self-theft

Similarities and differences between wait and sleep methods

Similarities and differences between wait and sleep

Similarities

  1. They all allow threads to block.
  2. They can all respond to interrupt: if they receive an interrupt signal during waiting, they can respond and throw an InterruptedException exception.

difference

  1. The wait method must be used in synchronized protected code, which is not required by the sleep method.
  2. When the sleep method is executed in the synchronization code, the monitor lock will not be released, but when the wait method is executed, the monitor lock will be released actively.
  3. In the sleep method, it is required to define a time. When the time expires, it will recover actively. For the wait method without parameters, it means to wait forever until it is interrupted or awakened. It will not recover actively.
  4. wait/notify is the method of Object class, while sleep is the method of Thread class.

summary

  1. notify will not release the lock. notify itself is in synchronized, and the lock will be released when the synchronized block ends.

  2. The thread calling wait is added to the wait set of the specified object and gives up the synchronization Declaration on the specified object, which is what we call releasing the lock

  3. Conditions for the thread to wake up again after calling wait:

    1. Other threads call notify with the specified object, and the thread happens to be awakened as a randomly selected thread
    2. Other threads call notifyAll with the specified object
    3. Other thread interrupts this thread
    4. The specified timeout time has passed (wait specifies the timeout time, such as locker.wait(1000)). When the timeout time is 0, the timeout time is ignored and the thread will sleep until it is awakened due to the above three factors
    5. A thread is falsely awakened (a thread can be awakened without notification, interruption, or timeout, which is called a false wake)

Keywords: Java Interview Multithreading

Added by first_lady_the_queen on Fri, 18 Feb 2022 17:54:13 +0200