← Back to Home

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