Multithreading — wait() & notify()

Java Jedi
4 min readSep 11, 2023

In this tutorial, we will shed lights on mythical methods of Object class — wait() and notify(). Questions related to these methods are often asked in technical interviews, so understanding these methods is very important while implementing concurrent applications and excelling at the interviews. Once I failed to answer during my interview😁️️️️️️.

These methods are mainly used to enable inter-communication of threads during their execution. They are used in situations when one thread may release lock on some common shared resource for some time, so that another thread may access shared resource, execute and relinquish the lock to the first thread. Then, the first thread continues its execution.

Since all the threads depend on intrinsic lock of a given object, we can implement inter-thread communication. It is possible with the help of wait() and notify(). Threads that are locking on the same intrinsic lock, or monitor, can release lock until the other thread calls notify.

wait () — owner thread releases the lock, the other thread waiting for the lock can acquire the lock and start execution. Owner thread will wait until it is notified or interrupted. If thread is interrupted while wait(), it throws InterruptedException

notify() — as soon as the thread finishes execution, it calls notify() to return the lock to the owner

wait() & notify() illustration

Let’s discuss the illustration above. As soon as thread#1 calls wait(), thread#2 starts executing. When thread#2 calls notify() -> the lock is returned back to thread#1 and it will continue executing.

Note: these methods wait() and notify() are used and called from synchronized blocks or methods since at the time of calling these methods, thread must be the owner of the lock, otherwise IllegalMonitorStateException will be thrown.

Let’s consider the following example.

Output:

// Output
Initial value: 1 //thread#1
Producer is executing, incrementing value //thread#1
Value = 2 //thread#1
- - - - - - - - - - - - -
Consumer started executing //thread#2
Value: 2 //thread#2
Value is incremented, value = 3 //thread#2
Notifying the other thread and releasing the lock //thread#2
- - - - - - - - - - - - -
Continuing executing after passing the lock to another thread. The lock has been acquired. //thread#1
Value: 3 //thread#1

It is a pretty straightforward example. There are two threads — thread#1, thread#2 and processor variable of class Processor. This Processor class has two synchronized methods — produce() and consume(). As the program runs, thread#1 calls produce() method and thread#2 calls consumer()
First, thread#1 acquires the lock, does some operations and calls wait(), at the same time, thread#2 already started execution and tries to acquire the lock that is owned by thread#1. As soon as thread#1 calls wait(), it enters a blocked / waiting state and releases the lock and thread#2 acquires the lock and continues execution; thread#2 does some operations and then calls notify() to return the lock to thread#1. Thread#1 then acquires the lock and resumes execution. This is a simple approach to achieve inter-thread communication.

Caveats of using wait() and notify()

Improper usage of wait() and notify() causes resource leakage that may lead to crashing of your system. Let’s discuss the following example.

This example is nearly identical to the example above with a small difference — there is no line where notify() is called in method consume(). The program flow is the same — thread#1 starts execution, acquires the lock, does some operation and then calls wait(). In the meantime, thread#2 acquires the lock after thread#1 call wait(), does some operations and finishes its execution. Thread#2 never calls notify() and thread#1 never acquires the lock again and remains blocked / waiting forever. This causes to resource leakage, the thread never finishes execution and cannot be used again or freed up, ultimately eating up resources and leading to crashing of the application overtime. Similar situation occurs when a thread dies before calling notify(). Hence, it is very important to call notify() to avoid this problem.

Additionally, to avoid this problem, it is possible to use other methods provided — wait(int timeoutMillis) and wait(int timeoutMillis, int nanos).
The thread will wait for specified amount of time and resumes execution if not notified.

notify() & notifyAll()

There is another method — notifyAll() to relinquish the lock. The difference between them is that notify() wake up single thread and notifyAll() wake all the threads competing to acquire the lock.

Inter-thread communication is commonly used in Pub/Sub pattern, where one thread acts a producer and the other acts as a consumer. To implement such systems, you need a proper synchronization mechanism and enable inter-thread communication between threads.

In this series of posts in Multithreading, we have discussed all the necessary concepts related mythical methods — wait() and notify(), how to implement inter-thread communication and caveats of it. Next, we will discuss locking mechanisms — Lock and its implementation ReentrantLock. Subscribe and stay tuned to be the first to read new posts.

--

--