Is notify() really a random wake-up?

Posted by bpgillett on Tue, 21 Dec 2021 14:19:19 +0100

Wake up mechanism of notify()

preface

As everyone who has learned java knows, when learning concurrent multithreading, we have a notify() method. At that time, when the blogger was learning, he said that notify() randomly wakes up a waiting thread and obtains a lock. I believe many small partners are the same as bloggers, but is this really the case?

notify()

Let's start with a simple thread wait() and notify () methods. I don't need to talk about the logic of the methods...

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Main {

    private static List<String> waitList = new LinkedList<>();
    private static List<String> notifyList = new LinkedList<>();
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 50; i++) {
            String threadName = String.valueOf(i);
            new Thread(() -> {
                synchronized (lock) {
                    String cthreadNmae = Thread.currentThread().getName();
                    System.out.println("Thread:" + cthreadNmae + "Waiting");
                    waitList.add(cthreadNmae);
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread:" + cthreadNmae + "Awakened");
                    notifyList.add(cthreadNmae);
                }
            }, threadName).start();
            TimeUnit.MILLISECONDS.sleep(50);
        }
        TimeUnit.SECONDS.sleep(1);
        for (int i = 1; i <= 50; i++) {
            synchronized (lock) {
                lock.notify();
                TimeUnit.MILLISECONDS.sleep(10);
            }
            
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println(waitList.toString());
        System.out.println(notifyList.toString());
    }


}

Run the above code:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
[1, 2, 5, 4, 3, 11, 10, 9, 8, 7, 6, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 50, 49]

It seems that notify() is really awakened randomly. At this time, I humbled my hand and corrected the code:

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Main {

    private static List<String> waitList = new LinkedList<>();
    private static List<String> notifyList = new LinkedList<>();
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 50; i++) {
            String threadName = String.valueOf(i);
            new Thread(() -> {
                synchronized (lock) {
                    String cthreadNmae = Thread.currentThread().getName();
                    System.out.println("Thread:" + cthreadNmae + "Waiting");
                    waitList.add(cthreadNmae);
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread:" + cthreadNmae + "Awakened");
                    notifyList.add(cthreadNmae);
                }
            }, threadName).start();
            TimeUnit.MILLISECONDS.sleep(50);
        }
        TimeUnit.SECONDS.sleep(1);
        for (int i = 1; i <= 50; i++) {
            synchronized (lock) {
                lock.notify();
                
            }
            TimeUnit.MILLISECONDS.sleep(10);
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println(waitList.toString());
        System.out.println(notifyList.toString());
    }


}

Run the code again:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]

Sleeping trough, what the hell is going on? Changed a line of code, and notify() is in order?
Curious, I decided to study it. After some thoughtful thinking, I found that the problem was sleep().

 for (int i = 1; i <= 50; i++) {
            synchronized (lock) {
                lock.notify();
                TimeUnit.MILLISECONDS.sleep(10);
            }
        }

After notify(), the waiting thread A is awakened to wait for the lock to be acquired, but sleep(), At this time, the synchronized () code block in the loop also needs to obtain the lock. Suppose that the code block obtains the lock at this time, the waiting thread A enters the "lock pool" to wait and wakes up another waiting thread B. at this time, there are two waiting threads A and B in the lock pool. Suppose that B obtains the lock, the wake-up sequence seems to be out of order. If sleep () is moved out, the synchronized code block will suspend taking the lock, Then the wake-up is orderly.

Why?
Take a look at the source code of notify():

 * Wakes up all threads that are waiting on this object's monitor. A
     * thread waits on an object's monitor by calling one of the
     * {@code wait} methods.
     * <p>
     * The awakened threads will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened threads
     * will compete in the usual manner with any other threads that might
     * be actively competing to synchronize on this object; for example,
     * the awakened threads enjoy no reliable privilege or disadvantage in
     * being the next thread to lock this object.
     * <p>
     * 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.

The general meaning of this note is:

**notify In the comments of the source code notify The selection of wake-up thread is arbitrary, but depends on the specific implementation jvm.**

As we all know, there are many ways to implement jvm, and the most mainstream is HotsPot.
Take a look at the implementation of notify() in HotsPot:

The synchronized wait() and notify() methods are located in ObjectMonitor. The notify() procedure calls the DequeueWaiter method:

In fact, the first node in the waiting queue is dequeued and locked. At this time, it is not difficult to understand why notify() is orderly!

Topics: Java Interview Multithreading Concurrent Programming