Inter-thread Communication in Java

Last Updated : 25 Apr, 2026

Inter-thread communication in Java enables threads to coordinate their execution by signaling each other during runtime. It is mainly used when multiple threads depend on shared resources or need to work in a specific sequence.

  • Helps avoid busy waiting and improves resource utilization
  • Ensures proper execution order among dependent threads
  • Commonly used in producer-consumer type problems

Note: Inter-thread communication is also known as Cooperation in Java.

Polling

Polling is the process of repeatedly checking a condition in a loop until it becomes true. Once the condition is satisfied, the required action is performed. It is commonly used when one thread waits for another to produce or update data.

Problem with Polling

  • Wastes CPU cycles due to continuous condition checking
  • Reduces efficiency and slows down overall execution
  • Keeps the thread busy instead of allowing other tasks to run

How Java Multithreading Handle Polling

Java avoids polling by using built-in communication methods that allow threads to wait efficiently instead of continuously checking a condition. These methods are defined in the Object class and must be used within a synchronized context. 

  • wait(): Releases the lock and puts the thread into a waiting state until notified
  • notify(): Wakes up one waiting thread (does not release the lock immediately)
  • notifyAll(): Wakes up all waiting threads on the same object

The image below demonstrates the concept of Thread Synchronization and Inter-Thread Communication in Java

Java Multithreading Tackles Polling

Producer-Consumer Problem

The Producer-Consumer problem involves two threads where one (producer) adds data to a shared queue and the other (consumer) removes data from it. Proper coordination is required to ensure the producer doesn’t add when the queue is full and the consumer doesn’t remove when it’s empty.

  • Uses wait(), notify(), and notifyAll() for synchronization
  • Ensures efficient communication between producer and consumer threads
  • Prevents issues like data inconsistency and unnecessary waiting

Example: A simple Java program to demonstrate the three methods. Please note that this program might only run in offline IDEs as it contains taking input at several points.

Java
import java.util.LinkedList;
import java.util.Queue;

public class Geeks {
    
    // Shared queue used by both producer and consumer
    private static final Queue<Integer> queue = new LinkedList<>();
    
    // Maximum capacity of the queue
    private static final int CAPACITY = 10;

    // Producer task
    private static final Runnable producer = new Runnable() {
        public void run() {
            while (true) {
                synchronized (queue) {
                    
                    // Wait if the queue is full
                    while (queue.size() == CAPACITY) {
                        try {
                            System.out.println("Queue is at max capacity");
                            queue.wait(); // Release the lock and wait
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // Add item to the queue
                    queue.add(10);
                    System.out.println("Added 10 to the queue");
                    queue.notifyAll(); // Notify all waiting consumers
                    try {
                        Thread.sleep(2000); 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };

    // Consumer task
    private static final Runnable consumer = new Runnable() {
        public void run() {
            while (true) {
                synchronized (queue) {
                    
                    // Wait if the queue is empty
                    while (queue.isEmpty()) {
                        try {
                            System.out.println("Queue is empty, waiting");
                            queue.wait(); // Release the lock and wait
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // Remove item from the queue
                    System.out.println("Removed " + queue.remove() + " from the queue");
                    queue.notifyAll(); // Notify all waiting producers
                    try {
                        Thread.sleep(2000); 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };

    public static void main(String[] args) {
        System.out.println("Main thread started");
        
        // Create and start the producer thread
        Thread producerThread = new Thread(producer, "Producer");
        
        // Create and start the consumer thread
        Thread consumerThread = new Thread(consumer, "Consumer");
        producerThread.start();
        consumerThread.start();
        System.out.println("Main thread exiting");
    }
}

Output:

Main thread started
Main thread exiting
Queue is empty, waiting
Added 10 to the queue
Removed 10 from the queue
Queue is empty, waiting
Added 10 to the queue
Removed 10 from the queue
...

Explanation: The program uses a shared queue for communication between producer and consumer threads. The producer adds items and waits if the queue is full, while the consumer removes items and waits if it is empty. Synchronization ensures only one thread accesses the queue at a time. The wait() method pauses execution until notified, and notifyAll() wakes up waiting threads. Both threads run continuously, with Thread.sleep() simulating processing delay.

Comment