Table of contents
1.
Introduction
2.
Understanding the Process of Inter-Thread Communication
3.
What is Polling, and what are the problems with it? 
4.
The Need for Synchronization
5.
The Role of wait(), notify(), and notifyAll()
5.1.
wait() Method:
5.2.
notify() Method:
5.3.
notifyAll() Method:
6.
Importance of synchronized Blocks or Methods
7.
Example of Inter-Thread Communication in Java
7.1.
Setting Up the Shared Resource
7.2.
Creating the Producer Thread
7.3.
Creating the Consumer Thread
7.4.
Running the Threads
8.
Difference Between Wait() and Sleep()
9.
Frequently Asked Questions 
9.1.
What are the potential risks of not using synchronization in inter-thread communication?
9.2.
Can inter-thread communication lead to deadlock? How?
9.3.
Is it possible to use wait() and notify() methods outside of synchronized blocks or methods?
10.
Conclusion
Last Updated: May 6, 2024
Easy

Inter Thread Communication in Java

Author Riya Singh
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

When multiple threads run in a Java program, they often need to communicate and synchronize their actions. This process, known as inter-thread communication, is crucial for ensuring that threads operate smoothly and efficiently without conflicts. Understanding and implementing this concept effectively is important for Java developers, especially in applications where tasks are divided into smaller, concurrent processes. 

inter thread communication in java

This article will delve into the essentials of inter-thread communication in Java, exploring its process, practical examples, and the benefits it brings to multi-threaded programming.

Understanding the Process of Inter-Thread Communication

In Java, threads are essentially independent paths of execution within a program. However, these paths often need to cross or interact, especially when they are working on related tasks. Inter-thread communication is the mechanism that enables these interactions in a controlled and predictable manner.

What is Polling, and what are the problems with it? 

Polling is a technique used in computer science to continuously check the status of a resource or device by sending inquiries at regular intervals. While simple to implement, polling suffers from several drawbacks. Firstly, it can result in excessive network traffic and resource consumption, especially in scenarios with frequent polling intervals or large-scale deployments. Secondly, it may introduce latency and delays in responding to real-time events, as the system must wait for the next polling cycle to detect changes. Additionally, continuous polling can drain device batteries in mobile applications, impacting user experience and device longevity. These limitations have led to the development of alternative techniques like event-driven architectures and push notifications to mitigate the issues associated with polling.

The Need for Synchronization

Threads operate in an asynchronous fashion, meaning they run independently and can start or complete tasks at different times. This independence, while useful for multitasking, can lead to issues when threads need to access shared resources or data. Synchronization is a technique used to ensure that only one thread can access a shared resource at a time, preventing conflicts and data corruption.

The Role of wait(), notify(), and notifyAll()

Java provides three fundamental methods for inter-thread communication: wait()notify(), and notifyAll(). These methods are part of the Object class, meaning they can be invoked on any object.

wait() Method:

The wait() method in Java is used in synchronization to make a thread wait until another thread invokes the notify() or notifyAll() method for the same object. It's typically used within synchronized blocks or methods. The syntax for the wait() method is:

public final void wait() throws InterruptedException

When a thread calls wait(), it releases the lock it holds, allowing other threads to enter synchronized blocks or methods on the same object. The thread then enters the waiting state until another thread invokes notify() or notifyAll() on the same object, or until a specified amount of time elapses if an optional timeout parameter is provided.

One common use case for wait() is in producer-consumer scenarios, where one thread produces data and another consumes it. By using wait() and notify(), producers can wait until there's data available for consumption without busy waiting, improving efficiency and resource utilization.

notify() Method:

The notify() method in Java is used to wake up a single thread that is waiting on the current object's monitor. It should be called from within a synchronized block or method. The syntax for the notify() method is:

public final void notify()

When a thread calls notify(), it signals to the JVM that another thread waiting on the same object can wake up. However, it's important to note that which thread gets awakened is not guaranteed and depends on factors like thread scheduling and priority.

notify() is often used in conjunction with wait() to implement inter-thread communication and coordination. For example, in a producer-consumer scenario, the producer thread can use notify() to wake up a consumer thread when new data is available for consumption.

notifyAll() Method:

The notifyAll() method in Java is similar to notify(), but it wakes up all threads that are waiting on the current object's monitor instead of just one. Its syntax is:

public final void notifyAll()

When a thread invokes notifyAll(), all threads that are currently in the waiting state on the same object's monitor are awakened. Like notify(), the order in which threads wake up is not guaranteed.

notifyAll() is useful in scenarios where multiple threads may be waiting for a specific condition to change. For instance, in a multi-threaded server application, notifyAll() can be used to signal all waiting threads when new client connections are accepted or when certain resources become available, ensuring fair access among competing threads.

Importance of synchronized Blocks or Methods

The wait(), notify(), and notifyAll() methods must be called from within a synchronized block or method. This requirement ensures that the calling thread holds the object's lock when these methods are invoked, maintaining thread safety and consistency.

Example of Inter-Thread Communication in Java

To illustrate inter-thread communication, let's consider a simple example involving two threads: Producer and Consumer. The Producer thread creates items and places them in a shared buffer, while the Consumer thread takes these items from the buffer for processing.

Setting Up the Shared Resource

First, we create a class representing the shared buffer. This class will have methods for putting and getting items, which will be used by Producer and Consumer threads.

class SharedBuffer {
    private int item;
    private boolean isAvailable = false;
    public synchronized void put(int item) {
        while (isAvailable) {
            try {
                wait();
            } catch (InterruptedException e) { }
        }
        this.item = item;
        isAvailable = true;
        notify();
    }
    public synchronized int get() {
        while (!isAvailable) {
            try {
                wait();
            } catch (InterruptedException e) { }
        }
        isAvailable = false;
        notify();
        return item;
    }
}

Creating the Producer Thread

The Producer thread will use the put() method to add items to the buffer.

class Producer extends Thread {
    private SharedBuffer buffer;
    public Producer(SharedBuffer buffer) {
        this.buffer = buffer;
    }
    public void run() {
        for (int i = 0; i < 10; i++) {
            buffer.put(i);
            System.out.println("Produced: " + i);
        }
    }
}

Creating the Consumer Thread

Similarly, the Consumer thread will use the get() method to retrieve and process items.

class Consumer extends Thread {
    private SharedBuffer buffer;
    public Consumer(SharedBuffer buffer) {
        this.buffer = buffer;
    }
public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Consumed: " + buffer.get());
        }
    }
}

Running the Threads

Finally, we create instances of Producer and Consumer and start the threads.

public class Main {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer();
        Producer producer = new Producer(buffer);
        Consumer consumer = new Consumer(buffer);


        producer.start();
        consumer.start();
    }
}

In this example, the wait() method in put() and get() ensures that the Producer waits if the buffer is full and the Consumer waits if the buffer is empty. The notify() method is used to wake up waiting threads when the state changes (new item added or consumed).

Difference Between Wait() and Sleep()

Wait() Sleep()
Used in synchronization. Used for pausing execution of a thread.
Must be called within synchronized block. Not required to be called within synchronized block.
Releases the lock it holds. Does not release any locks.
Can be woken up by notify() or notifyAll(). Cannot be woken up by another thread directly.
Part of the Object class. Part of the Thread class.
Throws InterruptedException. Does not throw InterruptedException.

Frequently Asked Questions 

What are the potential risks of not using synchronization in inter-thread communication?

Without synchronization, threads accessing shared resources can lead to data inconsistency and unpredictable behavior. For example, if multiple threads modify a shared variable simultaneously without synchronization, it can result in corrupted data. This problem is known as a "race condition."

Can inter-thread communication lead to deadlock? How?

Yes, inter-thread communication can lead to deadlock, particularly when two or more threads are waiting indefinitely for each other to release locks. For instance, if Thread A holds a lock on Object 1 and waits for a lock on Object 2, while Thread B holds a lock on Object 2 and waits for Object 1, both threads will wait indefinitely, creating a deadlock.

Is it possible to use wait() and notify() methods outside of synchronized blocks or methods?

No, invoking wait(), notify(), or notifyAll() outside of synchronized blocks or methods will throw an IllegalMonitorStateException. These methods require the calling thread to own the object's monitor, which is only possible within a synchronized block or method.

Conclusion

Inter-thread communication in Java is a fundamental concept that ensures smooth and coordinated operation between multiple threads, especially when dealing with shared resources. Through synchronization and the proper use of wait(), notify(), and notifyAll() methods within synchronized blocks or methods, Java developers can manage and control the interactions between threads, avoiding common pitfalls like race conditions and deadlocks. Understanding and implementing these mechanisms is essential for writing robust, efficient, and thread-safe Java applications.

You can refer to our guided paths on Code360. You can check our course to learn more about DSADBMSCompetitive ProgrammingPythonJavaJavaScript, etc. 

Also, check out some of the Guided Paths on topics such as Data Structure and AlgorithmsCompetitive ProgrammingOperating SystemsComputer Networks, DBMSSystem Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.

Live masterclass