Data Types and Operators
(Non-Primitive Data Types)
Control Flow Statements
Conditional Statements
Looping Statements
Branching Statements
Object-Oriented Programming (OOP)
Exception Handling
Collections Framework
Overview of Collections
Java I/O
Multithreading
GUI Programming with Swing
Advanced Topics
JAVA CODE
Java Basics
Working with Objects
Arrays, Conditionals, and Loops
Creating Classes and Applications in Java
More About Methods
Java Applet Basics
Graphics, Fonts, and Color
Simple Animation and Threads
More Animation, Images, and Sound
Managing Simple Events and Interactivity
Creating User Interfaces with the awt
Windows, Networking, and Other Tidbits
Modifiers, Access Control, and Class Design
Packages and Interfaces
Exceptions
Multithreading
Streams and I/O
Using Native Methods and Libraries
Under the Hood
Java Programming Tools
Working with Data Structures in Java
Advanced Animation and Media
Fun with Image Filters
Client/Server Networking in Java
Emerging Technologies
appendix A :- Language Summary
appendix B :- Class Hierarchy Diagrams
appendix C The Java Class Library
appendix D Bytecodes Reference
appendix E java.applet Package Reference
appendix F java.awt Package Reference
appendix G java.awt.image Package Reference
appendix H java.awt.peer Package Reference
appendix I java.io Package Reference
appendix J java.lang Package Reference
appendix K java.net Package Reference
appendix L java.util Package Reference
Inter-thread communication in Java is a mechanism that allows synchronized threads to communicate with each other, primarily for the purpose of efficient resource sharing and ensuring data consistency. The Java programming language provides built-in methods such as ‘wait()‘, ‘notify()‘, and ‘notifyAll()‘ to facilitate this communication. These methods are part of the ‘Object‘ class, and thus available to all objects.

Basics of Inter-Thread Communication

  1. wait() Method: This method causes the current thread to wait until another thread invokes the ‘notify()‘ method or the ‘notifyAll()‘ method for this object. The current thread must own the object’s monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object’s monitor to wake up.

  2. notify() Method: This method wakes up a single thread that is waiting on this object’s monitor. If multiple threads are waiting, one of them is chosen to be awakened. The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object.

  3. notifyAll() Method: This method wakes up all threads that are waiting on this object’s monitor. Like ‘notify()‘, the awakened threads will compete for the object’s monitor before they can proceed.

Example: Producer-Consumer Problem

The producer-consumer problem is a classic example of a multi-threaded scenario where inter-thread communication is essential. In this problem, one or more producer threads generate data and put it into a shared buffer, and one or more consumer threads take the data from the buffer.

Here’s how you can implement it in Java using ‘wait()‘, ‘notify()‘, and ‘notifyAll()‘:

Step 1: Define a Shared Buffer

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

class SharedBuffer {
    private final int capacity;
    private final Queue<Integer> queue = new LinkedList<>();

    public SharedBuffer(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void produce(int item) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();
        }
        queue.add(item);
        notifyAll();
    }

    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }
        int item = queue.poll();
        notifyAll();
        return item;
    }
}

				
			

Step 2: Define Producer and Consumer Classes

				
					class Producer implements Runnable {
    private final SharedBuffer buffer;

    public Producer(SharedBuffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        int item = 0;
        try {
            while (true) {
                buffer.produce(item);
                System.out.println("Produced: " + item);
                item++;
                Thread.sleep(100); // Simulate time taken to produce an item
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

class Consumer implements Runnable {
    private final SharedBuffer buffer;

    public Consumer(SharedBuffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        try {
            while (true) {
                int item = buffer.consume();
                System.out.println("Consumed: " + item);
                Thread.sleep(150); // Simulate time taken to consume an item
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

				
			

Step 3: Create and Start Threads

				
					public class ProducerConsumerExample {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer(5);

        Thread producerThread = new Thread(new Producer(buffer));
        Thread consumerThread = new Thread(new Consumer(buffer));

        producerThread.start();
        consumerThread.start();
    }
}

				
			

Explanation

  1. SharedBuffer Class: This class represents the shared buffer with a fixed capacity. It has two synchronized methods, produce() and consume(), which add and remove items from the buffer respectively.

  2. Producer Class: This class implements the Runnable interface and continuously produces items and puts them into the buffer. If the buffer is full, it waits.

  3. Consumer Class: This class also implements the Runnable interface and continuously consumes items from the buffer. If the buffer is empty, it waits.

  4. ProducerConsumerExample Class: This class contains the main method, which creates a shared buffer and starts both the producer and consumer threads.

Key Points

  • Synchronization: The synchronized keyword is used to ensure that only one thread can access the produce() or consume() method at a time, thus ensuring thread safety.
  • Wait and Notify: The wait() method is used to make a thread wait until a condition is met (e.g., buffer is not full for producer, buffer is not empty for consumer). The notify() method wakes up a waiting thread when a condition has changed (e.g., an item has been added or removed from the buffer). The notifyAll() method wakes up all waiting threads.

By using these mechanisms, threads can communicate and coordinate their actions efficiently, preventing issues such as deadlock and race conditions.

Scroll to Top