Inter-thread communication is a mechanism that allows threads to coordinate their actions by exchanging signals. This process is primarily facilitated in Java through the wait(), notify(), and notifyAll() methods, which enable threads to communicate rather than running in complete isolation.
Why this topic is important:
- It provides a structured way for threads to cooperate effectively.
- It eliminates the need for inefficient "busy waiting" cycles.
- It is a fundamental requirement for solving synchronization problems, such as the classic producer-consumer pattern.
- It represents a key competency in advanced Java multithreading.
Inter-thread communication enables threads to exchange information and synchronize their execution flow. Without this capability, threads operating on shared resources might execute in a conflicting order—such as a consumer thread attempting to access data before a producer thread has prepared it—leading to incorrect program behavior.
Through these communication methods, threads can:
- Signal other threads when a task is completed.
- Pause execution until a specific condition is met.
- Resume operation once another thread provides the necessary input.
These methods are declared in the java.lang.Object class and must be invoked within a synchronized context:
wait(): Causes the current thread to pause until another thread notifies it.notify(): Wakes up a single thread that is currently waiting on the object's monitor.notifyAll(): Wakes up all threads that are waiting on the object's monitor, which is generally considered safer when multiple threads are involved.
- Synchronization Requirement:
wait(),notify(), andnotifyAll()must only be called from within asynchronizedblock or method. - Lock Management: When a thread calls
wait(), it automatically releases the lock it holds on the object. - Post-Notification Behavior: After a thread is notified, it moves to a "ready" state but does not resume execution immediately; it must wait to re-acquire the object's lock.
- Condition Checking: Always use a
whileloop rather than anifstatement when checking conditions to guard against "spurious wakeups".
This example demonstrates how wait() and notify() coordinate a producer and a consumer thread safely.
class SharedResource {
private int value = 0;
private boolean flag = false;
synchronized void produce() {
while (flag) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
value = 10;
flag = true;
System.out.println("Producer produced: " + value);
notify();
}
synchronized void consume() {
while (!flag) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Consumer consumed: " + value);
flag = false;
notify();
}
}In scenarios involving multiple threads, notifyAll() is preferred to ensure all waiting threads are signaled, preventing potential deadlocks where a specific thread might remain waiting indefinitely.
// Inside a synchronized block
notifyAll(); // Wakes all waiting threads simultaneously- **Avoid
if**: Always usewhileloops for condition checking to maintain thread safety. - Synchronization: Ensure all communication methods are enclosed in
synchronizedblocks. - **Use
notifyAll()**: When in doubt, prefernotifyAll()overnotify()to ensure robust wake-up signaling. - Deadlock Prevention: Carefully manage locking logic to avoid circular wait conditions.