Java Notes
Complete guide to Inter-Thread Communication in Java — wait(), notify(), notifyAll(), producer-consumer pattern, and how threads coordinate using object monitors.
What is Inter-Thread Communication?
Inter-thread communication allows threads to coordinate their actions — one thread can signal another to wake up and check a condition. Without it, threads would have to use busy-waiting (constantly checking a condition), which wastes CPU.
| Consumer | Is data ready? No. Is data ready? No. Is data ready? No. YES! |
| Consumer | No data? I'll wait()... *sleeping* |
| Producer | Data ready! notify()! |
| Consumer | *wakes up* Got it! Processing... |
The Three Methods: wait(), notify(), notifyAll()
These methods are defined in java.lang.Object (not Thread!):
| Method | Purpose | Must be in | Lock behavior |
|---|---|---|---|
wait() | Thread releases lock and sleeps until notified | synchronized block | Releases lock |
notify() | Wakes up ONE waiting thread | synchronized block | Doesn't release lock immediately |
notifyAll() | Wakes up ALL waiting threads | synchronized block | Doesn't release lock immediately |
Critical Rule: These methods MUST be called from within a synchronized block/method on the SAME object, otherwise you get IllegalMonitorStateException.
Basic wait() and notify() Example
Output:
How wait/notify Works Internally
| │ Monitor Lock | [currently held by: Consumer] │ |
| │ Wait Set | [] (empty) │ |
| │ Entry Set | [] (empty) │ |
| Step 1 | Consumer calls lock.wait() |
| │ Monitor Lock | [FREE] │ |
| │ Wait Set | [Consumer] ← sleeping here │ |
| │ Entry Set | [] │ |
| Step 2 | Producer acquires lock, calls lock.notify() |
| │ Monitor Lock | [Producer] │ |
| │ Wait Set | [] ← Consumer moved out │ |
| │ Entry Set | [Consumer] ← ready to acquire lock │ |
| Step 3 | Producer exits synchronized, Consumer acquires lock |
| │ Monitor Lock | [Consumer] ← continues execution │ |
| │ Wait Set | [] │ |
| │ Entry Set | [] │ |
Producer-Consumer Pattern
The classic inter-thread communication pattern:
Output:
| Buffer size | 5 |
| [Producer] Produced | 1 | Buffer: [1] |
| [Consumer] Consumed | 1 | Buffer: [] |
| [Producer] Produced | 2 | Buffer: [2] |
| [Producer] Produced | 3 | Buffer: [3] |
| [Consumer] Consumed | 2 | Buffer: [3] |
| [Producer] Produced | 4 | Buffer: [3, 4] |
| [Producer] Produced | 5 | Buffer: [3, 4, 5] |
| [Consumer] Consumed | 3 | Buffer: [4, 5] |
| [Producer] Produced | 6 | Buffer: [4, 5, 6] |
notify() vs notifyAll()
Output:
Why Always Use while (Not if) with wait()
public class WhileNotIf {
public static void main(String[] args) {
System.out.println("=== Why while, not if ===\n");
System.out.println("❌ WRONG (if):");
System.out.println(" synchronized(lock) {");
System.out.println(" if (buffer.isEmpty()) { // Check ONCE");
System.out.println(" lock.wait();");
System.out.println(" }");
System.out.println(" // BUG: Another thread might have consumed");
System.out.println(" // the item between notify and re-acquiring lock!");
System.out.println(" process(buffer.remove()); // May fail!");
System.out.println(" }");
System.out.println("\n✅ CORRECT (while):");
System.out.println(" synchronized(lock) {");
System.out.println(" while (buffer.isEmpty()) { // RE-CHECK after wake");
System.out.println(" lock.wait();");
System.out.println(" }");
System.out.println(" // Guaranteed: buffer is NOT empty here");
System.out.println(" process(buffer.remove()); // Always safe!");
System.out.println(" }");
System.out.println("\nReasons to use while:");
System.out.println(" 1. Spurious wakeups (JVM may wake thread without notify)");
System.out.println(" 2. Other threads may change state between notify and re-acquire");
System.out.println(" 3. notifyAll() wakes ALL — only one should proceed");
}
}=== Why while, not if === ❌ WRONG (if): May process stale data after spurious wakeup ✓ CORRECT (while): Re-checks condition after every wakeup Always use while loop with wait()!
Real-World Example: Print Queue
=== Print Queue Demo === [+] Producer-1 added: Job-1 (queue size: 1) [+] Producer-2 added: Job-2 (queue size: 2) [-] Consumer took: Job-1 (queue size: 1) [+] Producer-1 added: Job-3 (queue size: 2) [Queue Full] Producer-2 waiting... [-] Consumer took: Job-2 (queue size: 1) [+] Producer-2 added: Job-4 (queue size: 2)
Common Mistakes
1. Calling wait/notify without synchronized
// ❌ IllegalMonitorStateException!
obj.wait(); // Must be in synchronized(obj)
obj.notify(); // Must be in synchronized(obj)
// ✅ Correct
synchronized (obj) {
obj.wait();
obj.notify();
}2. Using if instead of while with wait
// ❌ Vulnerable to spurious wakeups
if (condition) { lock.wait(); }
// ✅ Always re-check
while (condition) { lock.wait(); }3. Calling notify on wrong object
Object lock1 = new Object();
Object lock2 = new Object();
// Thread A:
synchronized (lock1) { lock1.wait(); }
// Thread B:
synchronized (lock2) { lock2.notify(); } // ❌ Won't wake A! Different object!Interview Questions
Q1: Why are wait(), notify(), notifyAll() in Object class, not Thread class?
Answer: Because every object in Java has an intrinsic lock (monitor). Threads wait ON objects and are notified BY objects. The lock is associated with the object, so the communication methods belong to Object.
Q2: What is the difference between notify() and notifyAll()?
Answer: notify() wakes ONE arbitrary thread from the wait set. notifyAll() wakes ALL threads in the wait set. Use notifyAll() when multiple threads might be waiting for different conditions on the same lock — it's safer.
Q3: Why must wait() be called in a while loop, not an if statement?
Answer: (1) Spurious wakeups — JVM can wake a thread without explicit notify. (2) Between notification and re-acquiring the lock, another thread may have changed the condition. (3) With notifyAll(), multiple threads wake but only one should proceed. The while loop re-checks the condition.
Q4: What happens if you call wait() without holding the object's lock?
Answer: IllegalMonitorStateException is thrown at runtime. wait(), notify(), and notifyAll() MUST be called from within a synchronized block/method on the same object.
Q5: Does notify() release the lock immediately?
Answer: No! The notifying thread continues to hold the lock until it exits the synchronized block. Only then can the notified thread re-acquire the lock and proceed. The notified thread moves from WAITING to BLOCKED (waiting for the lock).
Q6: What is the difference between wait() and sleep()?
Answer: wait() releases the object's lock and waits for notification (used for inter-thread communication). sleep() pauses the current thread for a specified time WITHOUT releasing any locks (just a time delay). wait() is called on an object; sleep() is a Thread static method.
Q8: Why must wait(), notify(), and notifyAll() be called inside synchronized blocks?
A: These methods require the calling thread to own the object's monitor (intrinsic lock). Without synchronization, the thread doesn't hold the monitor, and calling these throws IllegalMonitorStateException. The lock ensures that the condition check and wait/notify happen atomically, preventing race conditions.
Q9: What is a spurious wakeup and how do you handle it?
A: A spurious wakeup is when a thread returns from wait() without being notified or interrupted — it's an allowed JVM implementation behavior. Always check the wait condition in a WHILE loop (not IF): while (!condition) { wait(); }. This re-checks the condition after wakeup, going back to waiting if it was spurious.
Q10: What is the difference between notify() and notifyAll()?
A: notify() wakes ONE arbitrary waiting thread. notifyAll() wakes ALL waiting threads (they then compete for the lock). Use notifyAll() when multiple threads may be waiting for different conditions on the same object. Use notify() only when all waiters are waiting for the same condition and only one can proceed.
Q11: How does BlockingQueue simplify producer-consumer compared to wait/notify?
A: BlockingQueue encapsulates all synchronization internally. put() blocks if full, take() blocks if empty — no manual wait/notify needed. It handles spurious wakeups, monitor management, and condition checking internally. ArrayBlockingQueue, LinkedBlockingQueue, and PriorityBlockingQueue are common implementations.
Q12: What is the difference between wait() and sleep() in inter-thread communication?
A: wait(): releases the lock, waits for notify, used for thread coordination. sleep(): does NOT release any lock, pauses for fixed time, used for timing delays. wait() is for communication between threads; sleep() is for pausing a single thread independently.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Inter-Thread Communication in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, multithreading, inter, thread
Related Java Master Course Topics