The interviewer asked: What are the functions of wait and notify methods in threads?

2023.10.12

The interviewer asked: What are the functions of wait and notify methods in threads?


This article mainly summarizes some knowledge around the coordination and communication-related technologies between threads. Using the wait(), notify(), and notifyAll() methods in the Object class, coordination and communication between threads can be achieved, but they only Only synchronized methods/synchronized code blocks modified in synchronized will take effect.

1. Introduction

In the previous thread series article, we introduced the synchronized and volatile keywords, which can solve the problem of thread synchronization, but they cannot solve the problem of coordination and communication between threads.

To give a simple example, for example, thread A is responsible for accumulating the value of int type variable i to 10000, and then notifies thread B to print out the result.

How to achieve this? One of the simplest methods is that thread B continuously checks whether the conditions are met through polling while (i == 10000), so that it can be achieved.

Although this method can meet the requirements, it also brings another problem: the while() operation in thread B will not release CPU resources, which will cause the CPU to always perform judgment operations on this method, which is a huge waste of CPU resources.

We know that CPU resources are very, very expensive, because CPU resources are used not only by the current application, but also by many other applications. If these polling times are released and used by other threads, the running efficiency of the application can be significantly improved. For example, after the operation of thread A is completed, thread B is notified to perform subsequent operations. Thread B does not need to complete the coordination between threads through polling check. Is this better?

In the parent class of Java, that is, the Object class, there are three methods: wait(), notify(), notifyAll(), which can realize communication between threads.

If you are not exposed to multithreading, these methods may be basically unusable. Let’s take a look at how to use them!

2. Method introduction

  • wait()

The wait() method, as the name suggests, means waiting. Its function is to put the thread executing the current code into a blocked state, put the current thread into the "pre-execution queue", and stop execution at the code where wait() is located. Until notified or interrupted.

However, there is a premise. Before calling the wait() method, the thread must obtain the lock of the object, so the wait() method can only be called in the synchronized modified synchronization method/synchronization code block; at the same time, after the wait() method is executed, The acquired object lock will be released immediately for use by other threads. The current thread is blocked and enters a waiting state.

As for why wait() has a blocking effect, its internal mechanism is very complicated and is mainly implemented by the C code of the JVM. Everyone just needs to understand it.

  • notify()

The notify() method, as the name suggests, means notification. Its function is to re-awaken the waiting threads under the same monitor. If there are multiple threads waiting, then randomly select a waiting thread and notify it. notify() and make it wait to acquire the object lock of the object.

Pay attention to "waiting to acquire the object lock of the object", which means that even if the notification is received, the waiting thread will not acquire the object lock immediately, and must wait for the thread of the notify() method to release the lock.

The calling environment is the same as wait(), and notify() must also be called in the synchronized method/synchronized code block modified by synchronized.

  • notifyAll()

The notifyAll() method, as the name suggests, also means notification. Its function is to re-awaken all waiting threads under the same monitor. The notify() method will only randomly wake up a thread, and use the notifyAll() method. Will wake them all up at once.

Generally speaking, the notifyAll() method is safer, because when our code logic is not carefully considered, using notify() will cause only one thread to be awakened, and other threads may wait forever and never wake up.

The calling environment is the same as notify(), notifyAll() must also be called in the synchronized modified synchronization method/synchronization code block.

The three methods can be summarized as follows:

  • 1. The wait() method blocks the thread and enters the waiting state.
  • 2. notify() method, wake up the waiting thread, if there are multiple threads, randomly pick one from them
  • 3. notifyAll() method, wake up all waiting threads

2.1. Introduction to the use of wait/notify/notifyAll

Usually the wait() method is often used in conjunction with notify() or notifyAll().

Let's look at a simple example below.

public class MyThreadA extends Thread{

    private Object lock;

    public MyThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " wait begin");
            try {
                // 进入阻塞等待
                lock.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " wait end");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
public class MyThreadB extends Thread{

    private Object lock;

    public MyThreadB(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " notify begin");
            // 唤醒其它等待线程
            lock.notify();
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " notify end");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
public class MyThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        MyThreadA threadA = new MyThreadA(lock);
        threadA.start();

        //过3秒再启动下一个线程
        Thread.sleep(3000);

        MyThreadB threadB = new MyThreadB(lock);
        threadB.start();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

Run the service and the output will be as follows:

2023-09-28 16:42:19 当前线程:Thread-0 wait begin
2023-09-28 16:42:22 当前线程:Thread-1 notify begin
2023-09-28 16:42:22 当前线程:Thread-1 notify end
2023-09-28 16:42:22 当前线程:Thread-0 wait end
  • 1.
  • 2.
  • 3.
  • 4.

It can be concluded from the log that the threadA thread is started first, and then enters the blocked state. After 3 seconds, the threadB thread is started again. After the operation is completed, the threadA thread is notified that it can acquire the object lock, and finally the execution is completed.

The coordination and communication between the entire threads is roughly like this.

If we increase the number of threadA threads to 5, let's take a look at the running effect.

public class MyThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        // 创建5个wait线程
        for (int i = 0; i < 5; i++) {
            MyThreadA threadA = new MyThreadA(lock);
            threadA.start();
        }

        //过3秒再启动下一个线程
        Thread.sleep(3000);

        MyThreadB threadB = new MyThreadB(lock);
        threadB.start();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

Run the service and the output will be as follows:

2023-09-28 17:02:05 当前线程:Thread-0 wait begin
2023-09-28 17:02:05 当前线程:Thread-4 wait begin
2023-09-28 17:02:05 当前线程:Thread-3 wait begin
2023-09-28 17:02:05 当前线程:Thread-2 wait begin
2023-09-28 17:02:05 当前线程:Thread-1 wait begin
2023-09-28 17:02:08 当前线程:Thread-5 notify begin
2023-09-28 17:02:08 当前线程:Thread-5 notify end
2023-09-28 17:02:08 当前线程:Thread-0 wait end
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

From the log, it can be clearly seen that when multiple threads are in a waiting state, calling the notify() method will only wake up one of the waiting threads; at the same time, the service cannot be shut down because the remaining four threads have been blocked. state.

If we change the lock.notify() method in the MyThreadB class to the lock.notifyAll() method, let's see the effect.

public class MyThreadB extends Thread{

    private Object lock;

    public MyThreadB(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " notify begin");
            // 唤醒所有等待的线程
            lock.notifyAll();
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " notify end");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

Run the service and the output will be as follows:

2023-09-28 17:18:13 当前线程:Thread-0 wait begin
2023-09-28 17:18:13 当前线程:Thread-4 wait begin
2023-09-28 17:18:13 当前线程:Thread-3 wait begin
2023-09-28 17:18:13 当前线程:Thread-2 wait begin
2023-09-28 17:18:13 当前线程:Thread-1 wait begin
2023-09-28 17:18:16 当前线程:Thread-5 notify begin
2023-09-28 17:18:16 当前线程:Thread-5 notify end
2023-09-28 17:18:16 当前线程:Thread-1 wait end
2023-09-28 17:18:16 当前线程:Thread-2 wait end
2023-09-28 17:18:16 当前线程:Thread-3 wait end
2023-09-28 17:18:16 当前线程:Thread-4 wait end
2023-09-28 17:18:16 当前线程:Thread-0 wait end
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

It can be clearly seen from the log that all waiting threads are awakened after 3 seconds, and the service ends.

2.2. Introduction to wait release lock

In multi-threaded programming, you must pay attention to the lock at all times, because it plays an important role in whether the current code execution is safe.

As we mentioned above, calling the wait() method not only blocks the thread and enters the waiting state, but also releases the lock.

We can see it by looking at a simple example.

public class MyThreadA1 extends Thread{

    private Object lock;

    public MyThreadA1(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " wait begin");
            try {
                // 进入阻塞等待
                lock.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " wait end");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
public class MyThreadTest1 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        // 创建两个调用wait的线程
        MyThreadA1 threadA1 = new MyThreadA1(lock);
        threadA1.start();

        MyThreadA1 threadA2 = new MyThreadA1(lock);
        threadA2.start();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

Run the service and the output will be as follows:

2023-09-28 17:31:56 当前线程:Thread-0 wait begin
2023-09-28 17:31:56 当前线程:Thread-1 wait begin
  • 1.
  • 2.

It can be clearly seen from the log results that after one of the two threads called lock.wait(), it entered the blocking state and released the object lock. The other thread obtained the lock and entered the synchronization code block. So you see that both threads print wait begin.

There is also a sleep() method in the Thread class that can block the current thread, but there is a difference between them. The sleep() method will not let the current thread release the lock.

We can look at a simple example.

public class MyThreadA1 extends Thread{

    private Object lock;

    public MyThreadA1(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " sleep begin");
            try {
                // 进入阻塞等待
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " sleep end");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
public class MyThreadTest1 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        // 创建两个调用sleep的线程
        MyThreadA1 threadA1 = new MyThreadA1(lock);
        threadA1.start();

        MyThreadA1 threadA2 = new MyThreadA1(lock);
        threadA2.start();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

Run the service and the output will be as follows:

2023-09-28 17:55:20 当前线程:Thread-0 sleep begin
2023-09-28 17:55:21 当前线程:Thread-0 sleep end
2023-09-28 17:55:21 当前线程:Thread-1 sleep begin
2023-09-28 17:55:21 当前线程:Thread-1 sleep end
  • 1.
  • 2.
  • 3.
  • 4.

Judging from the log, the threads are not executed alternately, but serially.

2.3. Introduction to notify/notifyAll not releasing locks

Corresponding to this are notify() and notifyAll(). The current thread will not release the lock when calling the notify() or notifyAll() method. The lock will be released only when the synchronization method/synchronization code block is executed.

Again, we can look at a simple example.

public class MyThreadA2 extends Thread{

    private Object lock;

    public MyThreadA2(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " notify begin");
            // 唤醒其它等待线程
            lock.notify();
            System.out.println(DateUtil.format(new Date()) + " 当前线程:" + Thread.currentThread().getName() + " notify end");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
public class MyThreadTest2 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        // 创建两个调用notify()的线程
        MyThreadA2 threadA1 = new MyThreadA2(lock);
        threadA1.start();

        MyThreadA2 threadA2 = new MyThreadA2(lock);
        threadA2.start();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

Run the service and the output will be as follows:

2023-09-28 18:11:36 当前线程:Thread-0 notify begin
2023-09-28 18:11:36 当前线程:Thread-0 notify end
2023-09-28 18:11:36 当前线程:Thread-1 notify begin
2023-09-28 18:11:36 当前线程:Thread-1 notify end
  • 1.
  • 2.
  • 3.
  • 4.

It can be clearly seen from the log results that the two threads do not execute alternately, but execute serially.

2.4. IllegalMonitorStateException exception introduction

Although the wait(), notify(), and notifyAll() methods are in the Object class, theoretically each class can be called directly, but not everywhere can be called casually. If these three methods are called, they are not in the synchronized method/ In the synchronized code block, the exception java.lang.IllegalMonitorStateException will be thrown directly when the program is running.

Let's see a simple example below.

public class MyThreadTest3 {

    public static void main(String[] args) throws Exception {
        Object lock = new Object();
        lock.wait();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

Run the program and throw an exception directly.

Exception in thread "main" java.lang.IllegalMonitorStateException
 at java.lang.Object.wait(Native Method)
 at java.lang.Object.wait(Object.java:502)
 at com.example.thread.e3.MyThreadTest3.main(MyThreadTest3.java:19)
  • 1.
  • 2.
  • 3.
  • 4.

If replaced by notify() or notifyAll(), the result will be the same.

3. Summary

This article mainly summarizes some knowledge around the coordination and communication-related technologies between threads. Using the wait(), notify(), and notifyAll() methods in the Object class, coordination and communication between threads can be achieved, but they can only be used when synchronized Only modified synchronization methods/synchronization code blocks will take effect. If it is not called in a synchronized method/synchronized code block, a java.lang.IllegalMonitorStateException will be thrown.

It is inevitable that there are some omissions in the article. Netizens are welcome to leave comments and point them out!

4. Reference

1. Liao Xuefeng-Introduction to wait and notify

2. Cangjie in May - Introduction to wait() and notify()/notifyAll()