Thread synchronization
KNOWLEDGE GAP - LEARN MORE TO BE IMPLEMENTED
https://www.baeldung.com/java-wait-notify
Thread Synchronization in Java
In a multithreaded environment, multiple threads might try to modify the same resource. Not managing threads properly will of course lead to consistency issues.
Guarded Blocks in Java
One tool we can use to coordinate actions of multiple threads in Java is guarded blocks. Such blocks keep a check for a particular condition before resuming the execution.
With that in mind, we’ll make use of the following:
- Object.wait() to suspend a thread
- Object.notify() to wake a thread up
We can better understand this from the following diagram depicting the life cycle of a Thread:

There are many ways of controlling this life cycle. Let us focus only on wait() and notify().
wait(), notify() and notifyAll() are final methods defined in the Object class and can be called only from within a synchronized context. These methods can throw IllegalMonitorStateException if the current thread is not the owner of the object’s monitor.
The wait() Method
Simply put, calling wait() forces the current thread to wait until some other thread invokes notify() or notifyAll() on the same object.
For this, the current thread must own the object’s monitor. According to Javadocs, this can happen in the following ways:
- when we’ve executed synchronized instance method for the given object
- when we’ve executed the body of a synchronized block on the given object
- by executing synchronized static methods for objects of type Class
Note that only one active thread can own an object’s monitor at a time.
This wait() method comes with three overloaded signatures.
wait()
The wait() method causes the current thread to wait indefinitely until another thread either invokes notify() for this object or notifyAll().
wait(long timeout)
Using this method, we can specify a timeout after which a thread will be woken up automatically. A thread can be woken up before reaching the timeout using notify() or notifyAll().
Note that calling wait(0) is the same as calling wait().
wait(long timeout, int nanos)
This is yet another signature providing the same functionality. The only difference here is that we can provide higher precision.
The total timeout period (in nanoseconds) is calculated as 1_000_000*timeout + nanos.
notify() and notifyAll()
We use the notify() method for waking up threads that are waiting for access to this object’s monitor.
There are two ways of notifying waiting threads.
notify()
For all threads waiting on this object’s monitor (by using any one of the wait() methods), the method notify() notifies any one of them to wake up arbitrarily. The choice of exactly which thread to wake is nondeterministic and depends upon the implementation.
Since notify() wakes up a single random thread, we can use it to implement mutually exclusive locking where threads are doing similar tasks. But in most cases, it would be more viable to implement notifyAll().
notifyAll()
This method simply wakes all threads that are waiting on this object’s monitor.
The awakened threads will compete in the usual manner, like any other thread that is trying to synchronize on this object.
But before we allow their execution to continue, always define a quick check for the condition required to proceed with the thread. This is because there may be some situations where the thread got woken up without receiving a notification (this scenario is discussed later in an example).
Sender-Receiver Synchronization Problem
Let’s go through a simple Sender–Receiver application that will make use of the wait() and notify() methods to set up synchronization between them:
- The Sender is supposed to send a data packet to the Receiver.
- The Receiver cannot process the data packet until the Sender finishes sending it.
- Similarly, the Sender shouldn’t attempt to send another packet unless the Receiver has already processed the previous packet.
Let’s first create a Data class that consists of the data packet that will be sent from Sender to Receiver. We’ll use wait() and notifyAll() to set up synchronization between them:
package com.my.company.model;
public class Data {
private String packet;
// True if receiver should wait
// False if sender should wait
private boolean transfer = true;
public synchronized void send(String packet) {
while (!transfer) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread Interrupted");
}
}
transfer = false;
this.packet = packet;
notifyAll();
}
public synchronized String receive() {
while (transfer) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread Interrupted");
}
}
transfer = true;
String returnPacket = packet;
notifyAll();
return returnPacket;
}
}
Let’s break down what’s going on here:
- The
packet
variable denotes the data that is being transferred over the network. - We have a boolean variable
transfer
, which the Sender and Receiver will use for synchronization:- If this variable is true, the Receiver should wait for Sender to send the message.
- If it’s false, Sender should wait for Receiver to receive the message.
- The Sender uses the
send()
method to send data to the Receiver:- If transfer is false, we’ll wait by calling wait() on this thread.
- But when it is true, we toggle the status, set our message, and call notifyAll() to wake up other threads to specify that a significant event has occurred and they can check if they can continue execution.
- Similarly, the Receiver will use the
receive()
method:- If the transfer was set to false by Sender, only then will it proceed, otherwise we’ll call wait() on this thread.
- When the condition is met, we toggle the status, notify all waiting threads to wake up, and return the data packet that was received.
Why Enclose wait() in a while Loop?
Since notify() and notifyAll() randomly wake up threads that are waiting on this object’s monitor, it’s not always important that the condition is met. Sometimes the thread is woken up, but the condition isn’t actually satisfied yet.
We can also define a check to save us from spurious wakeups — where a thread can wake up from waiting without ever having received a notification.
Why Do We Need to Synchronize send() and receive() Methods?
We placed these methods inside synchronized methods to provide intrinsic locks. If a thread calling wait() method does not own the inherent lock, an error will be thrown.
Creating the Sender
We’ll now create Sender and implement the Runnable interface so that it’s instances can be executed by a thread.
package com.my.company.sender;
import com.my.company.model.Data;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.ThreadLocalRandom;
@RequiredArgsConstructor
public class Sender implements Runnable {
private final Data data;
@Override
public void run() {
String packets[] = {
"First packet",
"Second packet",
"Third packet",
"Fourth packet",
"End"
};
for (String packet : packets) {
data.send(packet);
// Thread.sleep() to mimic heavy server-side processing
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread Interrupted");
}
}
}
}
- We’re creating some random data packets that will be sent across the network in packets[] array.
- For each packet, we’re merely calling send().
- Then we’re calling Thread.sleep() with random interval to mimic heavy server-side processing.
Creating the Receiver
We’ll now create Receiver and implement the Runnable interface so that it’s instances can be executed by a thread.
package com.my.company.receiver;
import com.my.company.model.Data;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.ThreadLocalRandom;
@RequiredArgsConstructor
public class Receiver implements Runnable {
private final Data load;
@Override
public void run() {
for (String receivedMessage = load.receive();
!"End".equals(receivedMessage);
receivedMessage = load.receive()) {
System.out.println(receivedMessage);
//Thread.sleep() to mimic heavy server-side processing
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread Interrupted");
}
}
}
}
We are calling load.receive() in the loop until we get the last “End” data packet.
Running the application
package com.my.company;
import com.my.company.model.Data;
import com.my.company.receiver.Receiver;
import com.my.company.sender.Sender;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
Data data = new Data();
Thread sender = new Thread(new Sender(data));
Thread receiver = new Thread(new Receiver(data));
sender.start();
receiver.start();
}
}
Output:
We’ve received all data packets in the right, sequential order and successfully established the correct communication between our sender and receiver.
Hello World!
First packet
Second packet
Third packet
Fourth packet
Process finished with exit code 0
Conclusion
It is worth mentioning that all these low-level APIs, such as wait(), notify() and notifyAll(), are traditional methods that work well, but higher-level mechanisms are often simpler and better — such as Java’s native Lock and Condition interfaces (available in java.util.concurrent.locks package).
Inter thread communication
Communication between the threads of the same process.
Consider the classic queuing problem, where one thread is producing some data and another is consuming it. To make the problem more interesting, suppose that the producer has to wait until the consumer is finished before it generates more data.
In a polling system, the consumer would waste many CPU cycles while it waited for the producer to produce. Once the producer was finished, it would start polling, wasting more CPU cycles waiting for the consumer to finish, and so on.
Clearly, this situation is undesirable.
To avoid polling, Java includes an elegant interprocess communication mechanism via the following methods:
wait()
: This method tells the calling thread to give up the monitor and go to sleep until some other thread enters the same monitor and callsnotify()
.notify()
: This method wakes up the first thread that calledwait()
on the same object.notifyAll()
: This method wakes up all the threads that calledwait()
on the same object. The highest priority thread will run first.
These methods are implemented as final methods in Object, so all classes have them. All three methods can be called only from within a synchronized context.
These methods are declared within Object. Various forms of wait()
exist that allow you to specify a period of time to wait.
All these methods must be called within a try-catch block.
Why wait, notify and notifyAll is declared in Object Class instead of Thread?
KNOWLEDGE GAP - LEARN MORE - clarify this
- Wait and notify are not just normal methods or synchronization utility. More than that, they are communication mechanism between two threads in Java. And Object class is the correct place to make them available for every object if this mechanism is not available via any java keyword like synchronized. Remember synchronized and wait notify are two different areas and don’t confuse that they are same or related. Synchronized is to provide mutual exclusion and ensuring thread safety of Java class like race condition while wait and notify are communication mechanism between two thread.
- Locks are made available on per Object basis, which is another reason wait and notify is declared in Object class rather then Thread class.
- In Java in order to enter critical section of code, Threads needs lock and they wait for lock, they don’t know which threads holds lock instead they just know the lock is hold by some thread and they should wait for lock instead of knowing which thread is inside the synchronized block and asking them to release lock. this analogy fits with wait and notify being on object class rather than thread in Java.
Tags
External references
TO BE IMPLEMENTED
https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-concurrency-simple
For more information on the java.util.concurrent package, visit our overview of the java.util.concurrent article (https://www.baeldung.com/java-util-concurrent). And Lock and Condition are covered in the guide to java.util.concurrent.Locks (https://www.baeldung.com/java-concurrent-locks).