wait/notify mechanism for Java thread communication

Preface

Java thread communication is the process of associating individual threads so that threads can communicate with each other.For example, thread A modifies the value of an object and then notifies thread B so that thread B knows the value modified by thread A, which is thread communication.

<!-- more -->

wait/notify mechanism

One thread calls the Object's wait() method to block its thread; the other thread calls the Object's notify()/notifyAll() method, and the wait() blocked thread continues execution.

wai/notify method

Method Explain
wait() The current thread is blocked and the thread enters the WAITING state
wait(long) Set the thread blocking time, the thread will enter TIMED_WAITING status.Timeout returns if there is no notification within the set time (milliseconds)
wait(long, int) Thread blocking duration settings at nanosecond level
notify() Notifies a waiting thread on the same object that the wait() method has been executed and that the object lock has been acquired
notifyAll() Notify all waiting threads on the same object

Conditions for implementing the wait/notify mechanism:

  • The calling wait thread and the notify thread must have the same object lock.
  • The wait() method and notify()/notifyAll() method must be in the Synchronized method or code block.

Since the wait/notify method is defined inJava.lang.Object, so it can be used on any Java object.

wait method

The current thread must have acquired an object lock before executing the wait() method.When it is called, the current thread is blocked, entered a wait state, and suspended at the current wait ().At the same time, once the wait() method executes, the acquired object lock is immediately released.

Below is a case study of wait() releasing a lock.

First, review the code execution when you are not using the wait() method:

package top.ytao.demo.thread.waitnotify;

/**
 * Created by YangTao
 */
public class WaitTest {
    
    static Object object = new Object();
    
    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (object){
                System.out.println("Start Thread A");
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("End Thread A");
            }
        }, "thread A").start();


        new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("Start Thread B");

                System.out.println("End Thread B");
            }
        }, "thread B").start();

    }

}

Create threads A and B.Sleep is first created after the B thread, ensuring that the printing of the B thread is executed after the A thread.In thread A, the sleep time is longer than that of thread B when the object lock is acquired.

The result of execution is:

From the result of the figure above, you can see that the B thread must wait until the A thread executes the synchronize code block and releases the object lock before the A thread acquires the object lock and enters the synchronize code block.During this process, the Thread.sleep() method also does not release the lock.

When the wait() method is currently executed in the A-thread synchronize code block, the object lock is actively released, and the A-thread code is as follows:

new Thread(() -> {
    synchronized (object){
        System.out.println("Start Thread A");
        try {
            // Call the wait method of the object object object
            object.wait();
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End Thread A");
    }
}, "thread A").start();

Execution results:

Thread A is also blocked and will not print the end thread A.

The wait(long) method sets a time-out, and when the wait time is greater than the set time-out, it continues to execute code after the wait(long) method.

new Thread(() -> {
    synchronized (object){
        System.out.println("Start Thread A");
        try {
            object.wait(1000);
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End Thread A");
    }
}, "thread A").start();

results of enforcement

Similarly, the wait(long, int) method, like wait(long), is only a time setting at multiple nanosecond levels.

notify method

Similarly, the current thread must have acquired a thread lock before executing the notify() method.When the notify () method is called, a blocked wait thread executing the wait() method is notified, causing the wait thread to regain the object lock and continue executing the code following wait().However, unlike the wait() method, an object lock is not released immediately after executing notify (), but only after executing a code block or method of synchronization, so the thread receiving the notification does not get the lock immediately, nor does it need to wait for the thread executing the notify () method to release the lock before it is acquired.

notify()

The following is the use of the notify() method, which implements a complete wait/notify example, and verifies that the thread executing the notify() method immediately releases the lock after notification is given, and the thread executing the wait() method immediately acquires the lock.

package top.ytao.demo.thread.waitnotify;

/**
 * Created by YangTao
 */
public class WaitNotifyTest {

    static Object object = new Object();

    public static void main(String[] args) {
        System.out.println();

        new Thread(() -> {
            synchronized (object){
                System.out.println("Start Thread A");
                try {
                    object.wait();
                    System.out.println("A Thread reacquires lock and proceeds");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("End Thread A");
            }
        }, "thread A").start();


        new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("Start Thread B");
                object.notify();
                System.out.println("thread B Notify Thread Complete A");
                try {
                    // Whether thread A can acquire locks immediately after experimenting with the notify() method
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("End Thread B");
            }
        }, "thread B").start();

    }

}

The above A thread executes the wait() method and the B thread executes the notify() method, resulting in:

As you can see from the execution results, after the B thread executes the notify() method, the A thread does not acquire the lock even if it sleep s, and the notify() method does not release the lock.

Notfy () is a thread that notifies a waiting thread, but only one waiting thread that executes the wait() method can be notified by calling the notify() method once.If there are multiple waiting threads, the notify() method needs to be called multiple times, and the order of notifications to threads is based on the order in which the wait() method is executed.

The code below creates two threads that execute the wait() method:

package top.ytao.demo.thread.waitnotify;

/**
 * Created by YangTao
 */
public class MultiWaitNotifyTest {

    static Object object = new Object();

    public static void main(String[] args) {
        System.out.println();

        new Thread(() -> {
            synchronized (object){
                System.out.println("Start Thread A");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("End Thread A");
            }
        }, "thread A").start();


        new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("Start Thread B");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("End Thread B");
            }
        }, "thread B").start();


        new Thread(() -> {
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("Start Notification Thread C");
                object.notify();
                object.notify();
                System.out.println("End Notification Thread C");
            }
        }, "thread C").start();

    }

}

First the A thread executes the wait() method, then the B thread executes the wait() method, and finally the C thread calls the notify() method twice to execute the result:

notifyAll()

Notify threads of multiple wait states. The scenario implemented by calling notify() method multiple times makes the implementation less friendly in practice. If you want to notify threads of all wait states, you can use the notifyAll() method to wake up all threads.

To achieve this, simply change the part of the C thread that called the notify() method more than once to call the notifyAll() method once.

new Thread(() -> {
    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    synchronized (object){
        System.out.println("Start Notification Thread C");
        object.notifyAll();
        System.out.println("End Notification Thread C");
    }
}, "thread C").start();

Execution results:

The wake-up order of notifyAll() varies depending on the implementation of different JVM s. Threads are waked up in reverse order in the current test environment.

Implementing a producer-consumer model

The production consumer mode is where one thread produces data for storage and another thread consumes data for extraction.The following two threads are simulated. The producer generates a UUID to store in the List object. The consumer reads the data in the List object and clears it when it is finished.

The implementation code is as follows:

package top.ytao.demo.thread.waitnotify;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Created by YangTao
 */
public class WaitNotifyModelTest {

    // Store data generated by producers
    static List<String> list = new ArrayList<>();

    public static void main(String[] args) {

        new Thread(() -> {
            while (true){
                synchronized (list){
                    // Determine if there is data in the list, and if there is data, wait until the data is consumed
                    if (list.size() != 0){
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    // When there is no data in the list, the generated data is added to the list
                    list.add(UUID.randomUUID().toString());
                    list.notify();
                    System.out.println(Thread.currentThread().getName() + list);
                }
            }
        }, "Producer Threads A ").start();


        new Thread(() -> {
            while (true){
                synchronized (list){
                    // If there is no data in the list, it goes into a wait state and waits until a data notification is received before continuing
                    if (list.size() == 0){
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    // Read data when there is data
                    System.out.println(Thread.currentThread().getName() + list);
                    list.notify();
                    // Read, clear the current UUID data
                    list.clear();
                }
            }
        }, "Consumer Threads B ").start();

    }

}

Run result:

When the producer thread is running, if there is already unconsumed data, the current thread enters a wait state, receives notification that the data has been consumed, and then continues to add data to the list.

When the consumer thread runs, if there is no unconsumed data, the current thread enters a waiting state, receives notification that new data has been added to the List, continues executing code consumption data, and clears.

For both producers and consumers, based on object locks, only one thread can acquire them at a time. If a producer acquires a lock, it verifies whether data needs to be generated. If a consumer acquires a lock, it verifies whether data is consumable.

A simple producer-consumer model is done.

summary

Wait/Notify mechanism is a way to achieve communication between Java threads. In multithreads, individual running threads communicate with each other to work together more efficiently and make more efficient use of CPU handlers.This is also a must for learning or studying Java threads.

Recommended reading

Java Thread Foundation, Start Here

JDK Dynamic Proxy and CGLIB Dynamic Proxy that You Must Meet

Dubbo Series

Tags: Java less jvm JDK

Posted on Fri, 15 May 2020 10:05:56 -0700 by ScoTi