Back to the 2020 paper

Module III: Basics of Web Programming

20207m

What is multithreading in Java? Explain the inter-thread communication with the help of suitable example.

Worked SolutionAI Assisted

Answer: Multithreading and Inter-Thread Communication in Java

1. What is Multithreading in Java?

Multithreading is a core Java feature that allows concurrent execution of two or more parts of a program (called threads) to maximize CPU utilization. Each thread represents an independent path of execution sharing common memory space.

Ways to Create Threads in Java:

  1. By extending the Thread class and overriding run().
  2. By implementing the Runnable interface and passing it to a Thread instance.

2. What is Inter-Thread Communication?

Inter-thread communication (cooperation) allows synchronized threads to communicate with each other regarding resource availability and state changes, avoiding CPU-wasting polling loops (busy waiting).

Core Methods (Defined in java.lang.Object):

  • wait(): Causes the current thread to release the monitor lock and wait until another thread invokes notify() or notifyAll() on the same object.
  • notify(): Wakes up a single thread waiting on this object's monitor.
  • notifyAll(): Wakes up all threads waiting on this object's monitor.

Rule: wait(), notify(), and notifyAll() must always be executed inside a synchronized block or method.

+-------------------+                               +-------------------+
|  Producer Thread  |                               |  Consumer Thread  |
+---------+---------+                               +---------+---------+
          |                                                   |
          | 1. Produces Item                                  |
          | 2. Puts item in Shared Buffer                     |
          | 3. Calls notify() ------------------------------> | (Wakes Up)
          | 4. Buffer Full -> Calls wait() (Releases Lock)    | 5. Consumes Item
          |                                                   | 6. Calls notify()
          |<--------------------------------------------------|
     (Wakes Up)

3. Producer-Consumer Implementation Example

// Shared resource buffer
class SharedQueue {
    private int data;
    private boolean hasData = false;

    // Producer calls put()
    public synchronized void put(int value) {
        while (hasData) {
            try {
                wait(); // Wait if buffer already has data
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        this.data = value;
        this.hasData = true;
        System.out.println("Produced: " + value);
        notify(); // Notify the waiting consumer
    }

    // Consumer calls get()
    public synchronized int get() {
        while (!hasData) {
            try {
                wait(); // Wait if buffer is empty
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        this.hasData = false;
        System.out.println("Consumed: " + data);
        notify(); // Notify the waiting producer
        return data;
    }
}

// Producer Thread
class Producer extends Thread {
    private SharedQueue queue;

    Producer(SharedQueue q) { this.queue = q; }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            queue.put(i);
            try { Thread.sleep(500); } catch (InterruptedException ignored) {}
        }
    }
}

// Consumer Thread
class Consumer extends Thread {
    private SharedQueue queue;

    Consumer(SharedQueue q) { this.queue = q; }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            queue.get();
            try { Thread.sleep(800); } catch (InterruptedException ignored) {}
        }
    }
}

// Main Driver Class
public class InterThreadDemo {
    public static void main(String[] args) {
        SharedQueue queue = new SharedQueue();
        Producer p = new Producer(queue);
        Consumer c = new Consumer(queue);

        p.start();
        c.start();
    }
}

Sample Output:

Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
Produced: 3
Consumed: 3
...

4. Key Takeaways

  1. Thread Synchronization: Prevents race conditions and dirty reads on shared data.
  2. Lock Release: Calling wait() immediately releases the object monitor lock, whereas Thread.sleep() retains the lock.
  3. Deadlock Prevention: Coordinated wait() and notify() calls ensure producer and consumer threads run without starvation or deadlock.

Similar questions