Inter-Thread Communication
Inter-thread communication in Java allows multiple threads to coordinate and cooperate when working on shared data. It is primarily achieved using the wait(), notify(), and notifyAll() methods of the Object class, in conjunction with synchronization. This is a very high-frequency interview topic, especially with producer–consumer scenarios.
Why Inter-Thread Communication Is Needed
Without coordination, threads may:
- Run in the wrong order
- Waste CPU cycles (busy waiting)
- Read inconsistent data
- Cause race conditions
Goal: Enable threads to pause, resume, and signal each other safely.
Core Methods for Inter-Thread Communication
All belong to java.lang.Object:
| Method | Purpose |
|---|---|
| wait() | Releases lock and waits |
| notify() | Wakes up one waiting thread |
| notifyAll() | Wakes up all waiting threads |
Important Rule (Interview Favorite)
wait(), notify(), and notifyAll() must be called inside a synchronized context.
Otherwise: IllegalMonitorStateException
How wait() Works
- Causes the current thread to release the monitor lock
- Thread enters WAITING or TIMED_WAITING state
- Thread resumes only after:
- notify() / notifyAll()
- Re-acquiring the lock
synchronized (lock) {
lock.wait();
}
How notify() Works
- Wakes one arbitrary waiting thread
- Does not release the lock immediately
- Lock is released only after synchronized block exits
synchronized (lock) {
lock.notify();
}
How notifyAll() Works
- Wakes all waiting threads
- Threads compete to re-acquire the lock
- Safer when multiple conditions exist
synchronized (lock) {
lock.notifyAll();
}
Thread State Transitions
| Method | State Change |
|---|---|
| wait() | RUNNABLE → WAITING |
| notify() | WAITING → BLOCKED |
| Lock acquired | BLOCKED → RUNNABLE |
Classic Example: Producer–Consumer Problem
Shared Resource
class SharedBuffer {
private int data;
private boolean available = false;
synchronized void produce(int value) throws InterruptedException {
while (available) {
wait();
}
data = value;
available = true;
System.out.println("Produced: " + value);
notify();
}
synchronized int consume() throws InterruptedException {
while (!available) {
wait();
}
available = false;
System.out.println("Consumed: " + data);
notify();
return data;
}
}
✔ Uses while, not if
✔ Avoids spurious wakeups
Why while Is Used Instead of if (Very Important)
- Threads may wake up without condition being true
- Multiple threads may be notified
- while rechecks condition
while (!condition) {
wait();
}
wait() vs sleep() (Common Interview Trap)
| Aspect | wait() | sleep() |
|---|---|---|
| Releases lock | ✔ Yes | ❌ No |
| Called on | Object | Thread |
| Used for | Coordination | Pause |
| Requires synchronized | ✔ Yes | ❌ No |
notify() vs notifyAll()
| Aspect | notify() | notifyAll() |
|---|---|---|
| Threads awakened | One | All |
| Performance | Better | Slower |
| Safety | Risky | Safer |
| Use case | Single-condition | Multiple conditions |
Common Mistakes
- Calling wait() outside synchronized block
- Using if instead of while
- Using notify() when multiple threads wait
- Synchronizing on wrong object
- Forgetting condition flags
Best Practices (Production-Grade)
- Always use wait() in a while loop
- Prefer notifyAll() for correctness
- Keep synchronized blocks small
- Clearly define condition variables
- Consider java.util.concurrent alternatives
Modern Alternatives (Preview)
Instead of low-level wait/notify:
- BlockingQueue
- CountDownLatch
- CyclicBarrier
- Semaphore
✔ Safer
✔ Easier to maintain
✔ Preferred in enterprise code
Interview-Ready Answers
Short Answer
Inter-thread communication allows threads to coordinate using wait, notify, and notifyAll.
Detailed Answer
In Java, inter-thread communication is achieved using wait(), notify(), and notifyAll() methods of the Object class. These methods allow threads to release locks, wait for conditions, and signal other threads, enabling safe coordination and avoiding busy waiting in multithreaded programs.
Key Takeaway
wait() releases the lock.
notify() signals waiting threads.
notifyAll() ensures correctness.
Inter-thread communication is powerful but error-prone—use it carefully or prefer high-level concurrency utilities.
Inter-Thread Communication Examples (Interview Pack)
1. Basic wait() and notify()
class Demo {
static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread waiting");
lock.wait();
System.out.println("Thread resumed");
} catch (Exception e) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Notifier");
lock.notify();
}
});
t1.start();
t2.start();
}
}
2. wait() Releases Lock, sleep() Does NOT
class Demo {
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (Exception e) {}
}
}).start();
}
}
Key Point
- wait() → releases monitor
- sleep() → keeps monitor
3. Illegal Monitor State (Interview Trap)
class Demo {
public static void main(String[] args) throws Exception {
Object lock = new Object();
// lock.wait(); // ❌ IllegalMonitorStateException
}
}
Rule: wait() / notify() must be inside synchronized.
4. Producer–Consumer (Single Item)
class Store {
int item;
boolean available = false;
synchronized void produce(int value) throws Exception {
while (available) wait();
item = value;
available = true;
notify();
}
synchronized int consume() throws Exception {
while (!available) wait();
available = false;
notify();
return item;
}
}
5. Producer–Consumer with Threads
class Demo {
public static void main(String[] args) {
Store store = new Store();
new Thread(() -> {
try { store.produce(10); } catch (Exception e) {}
}).start();
new Thread(() -> {
try { System.out.println(store.consume()); } catch (Exception e) {}
}).start();
}
}
6. Why while Instead of if (Spurious Wakeup)
while (!condition) {
wait();
}
Explanation:
- Thread may wake up without notify
- Always re-check condition
7. notify() vs notifyAll()
class Demo {
synchronized void resumeOne() {
notify(); // wakes one thread
}
synchronized void resumeAll() {
notifyAll(); // wakes all waiting threads
}
}
8. Multiple Waiting Threads + notifyAll()
class Demo {
static final Object lock = new Object();
public static void main(String[] args) {
Runnable task = () -> {
synchronized (lock) {
try {
lock.wait();
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {}
}
};
new Thread(task, "T1").start();
new Thread(task, "T2").start();
synchronized (lock) {
lock.notifyAll();
}
}
}
9. Ordered Execution (T1 → T2 → T3)
class Order {
int turn = 1;
synchronized void run(int myTurn) throws Exception {
while (turn != myTurn) wait();
System.out.println("Thread " + myTurn);
turn++;
notifyAll();
}
}
class Demo {
public static void main(String[] args) {
Order o = new Order();
new Thread(() -> { try { o.run(1); } catch (Exception e) {} }).start();
new Thread(() -> { try { o.run(2); } catch (Exception e) {} }).start();
new Thread(() -> { try { o.run(3); } catch (Exception e) {} }).start();
}
}
10. Inter-Thread Communication with Shared Flag
class Flag {
boolean ready = false;
synchronized void waitForSignal() throws Exception {
while (!ready) wait();
System.out.println("Signal received");
}
synchronized void sendSignal() {
ready = true;
notify();
}
}
11. Using join() as Communication
class Demo {
public static void main(String[] args) throws Exception {
Thread worker = new Thread(() -> {
try { Thread.sleep(300); } catch (Exception e) {}
System.out.println("Worker done");
});
worker.start();
worker.join();
System.out.println("Main continues");
}
}
12. Missed Signal Problem (Bad Design)
// notify happens before wait → signal lost
Fix
- Use condition variable + while
- Or higher-level concurrency APIs
13. wait(long timeout)
class Demo {
synchronized void timedWait() throws Exception {
wait(500);
System.out.println("Timed wait over");
}
}
14. Multiple Conditions on Same Lock (Complex Case)
class Resource {
boolean readReady = false;
boolean writeReady = false;
synchronized void read() throws Exception {
while (!readReady) wait();
System.out.println("Reading");
}
synchronized void write() throws Exception {
while (!writeReady) wait();
System.out.println("Writing");
}
}
15. Inter-Thread Communication Using volatile (Simple Signal)
class Signal implements Runnable {
volatile boolean running = true;
public void run() {
while (running) {}
System.out.println("Stopped");
}
public static void main(String[] args) {
Signal s = new Signal();
new Thread(s).start();
s.running = false;
}
}
16. Why wait/notify Are Low-Level
- Error-prone
- Easy to deadlock
- Hard to maintain
Interview Tip: Prefer BlockingQueue, CountDownLatch.
17. Replacing wait/notify with BlockingQueue
import java.util.concurrent.*;
class Demo {
public static void main(String[] args) throws Exception {
BlockingQueue<Integer> q = new ArrayBlockingQueue<>(1);
new Thread(() -> {
try { q.put(10); } catch (Exception e) {}
}).start();
System.out.println(q.take());
}
}
18. Inter-Thread Communication Using CountDownLatch
import java.util.concurrent.*;
class Demo {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
System.out.println("Waiting");
try { latch.await(); } catch (Exception e) {}
System.out.println("Resumed");
}).start();
Thread.sleep(300);
latch.countDown();
}
}
19. Common Interview Trap
notify(); // called outside synchronized → ❌ IllegalMonitorStateException
20. Interview Summary – Inter-Thread Communication
- wait()
- notify()
- notifyAll()
- join()
Key Points
- Must hold monitor lock
- wait() releases lock
- Use while, not if
- notifyAll() safer than notify
- Prefer high-level concurrency APIs in real projects