Interthread Communication in java

Interthread communication in Java is a mechanism by which threads can communicate and synchronize their actions to ensure that they operate coherently. It involves coordination between threads to control their execution and manage shared resources. Java provides two fundamental methods for interthread communication: wait() and notify() (and their variants, notifyAll()).

wait(), notify(), and notifyAll() Methods:

  1. wait():
  • The wait() method is used by a thread to release the lock it holds and enter into a waiting state. It is typically used when a thread wants to wait for a specific condition to be satisfied.
  1. notify():
  • The notify() method is used by a thread to wake up another thread that is waiting. It notifies one of the waiting threads that it can proceed. If multiple threads are waiting, it’s not guaranteed which thread will be awakened.
  1. notifyAll():
  • The notifyAll() method is used to wake up all the threads that are currently waiting. It’s often preferred over notify() to ensure that all waiting threads have a chance to proceed.

Example of Interthread Communication:

Here’s a simple example illustrating the use of wait(), notify(), and notifyAll():

class SharedResource {
    boolean isDataReady = false;

    synchronized void produceData() {
        // Produce data
        System.out.println("Data produced");
        isDataReady = true;

        // Notify waiting consumer threads
        notifyAll();
    }

    synchronized void consumeData() {
        // Wait until data is ready
        while (!isDataReady) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // Consume data
        System.out.println("Data consumed");
        isDataReady = false;
    }
}

class Producer extends Thread {
    private SharedResource sharedResource;

    public Producer(SharedResource sharedResource) {
        this.sharedResource = sharedResource;
    }

    public void run() {
        sharedResource.produceData();
    }
}

class Consumer extends Thread {
    private SharedResource sharedResource;

    public Consumer(SharedResource sharedResource) {
        this.sharedResource = sharedResource;
    }

    public void run() {
        sharedResource.consumeData();
    }
}

public class InterthreadCommunicationExample {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();
        Producer producer = new Producer(sharedResource);
        Consumer consumer = new Consumer(sharedResource);

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

In this example, the Producer produces data and notifies waiting consumer threads using notifyAll(). The Consumer waits until data is ready and then consumes it.

Best Practices for Interthread Communication:

  1. Always use the wait() method within a loop:
  • It helps guard against spurious wake-ups.
while (!condition) {
    try {
        wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
  1. Avoid holding locks during lengthy operations:
  • To minimize contention, release the lock before performing time-consuming tasks.
synchronized (lock) {
    // Release the lock
    lock.wait();
    // Acquire the lock again
}
  1. Use notifyAll() cautiously:
  • It wakes up all waiting threads, which might introduce unnecessary contention. Only use it when necessary.

Interthread communication is an essential concept in concurrent programming to ensure proper coordination and synchronization between threads sharing resources. It helps prevent race conditions and ensures that threads operate in a synchronized and orderly manner.

What is Polling ?

Polling is usually implemented by a loop that is used to check some conditions repeatedly. Once the condition is true, appropriate action is taken. This wastes CPU time. For example, consider the classic queuing problem, where one thread is producing some data and another is consuming it. To make the problem more interesting, suppose that the producer has to wait until the consumer is finished before it generates more data. In a polling system, the consumer would waste many CPU cycles while it waited for the producer to produce. Once the producer was finished, it would start polling, wasting more CPU cycles waiting for the consumer to finish

To avoid polling, Java includes an elegant interprocess communication mechanism via the wait( ), notify( ), and notifyAll( ) methods. These methods are implemented as final methods in Object

so all classes have them. All three methods can be called only from within a synchronized context. Although conceptually advanced from a computer science perspective, the rules for using these methods are actually quite simple

  • wait( ) tells the calling thread to give up the monitor and go to sleep until some other thread enters the same monitor and calls notify( ).
  • notify( ) wakes up a thread that called wait( ) on the same object
  • notifyAll( ) wakes up all the threads that called wait( ) on the same object. One of the threads will be granted access

These methods are declared within Object

final void wait( ) throws InterruptedException
final void notify( )
final void notifyAll( )

An incorrect implementation of a producer and consumer

class Q {
int n;
synchronized int get() {
System.out.println("Got: " + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out.println("Put: " + n);
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while(true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while(true) {
q.get();
}
}
}
class PC {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}

the put( ) and get( ) methods on Q are synchronized, nothing stops the producer from overrunning the consumer, nor will anything stop the consumer from consuming the same queue value twice. Thus, you get the erroneous output shown here (the exact output will vary with processor speed and task load)

Output

Put: 1
Got: 1
Got: 1
Got: 1
Got: 1

Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7

The proper way to write this program in Java is to use wait( ) and notify( ) to signal in both directions

A correct implementation of a producer and consumer.

The proper way to write this program in Java is to use wait( ) and notify( ) to signal in both directions

class Q {
int n;
boolean valueSet = false;
synchronized int get() {
while(!valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
while(valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
valueSet = true;
System.out.println("Put: " + n);
notify();
}
}
class Producer implements Runnable {
Q q;

Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while(true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while(true) {
q.get();
}
}
}
class PCFixed {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}

Inside get( ), wait( ) is called. This causes its execution to suspend until the Producer notifies you that some data is ready. When this happens, execution inside get( ) resumes. After the data has been obtained, get( ) calls notify( ). This tells Producer that it is okay to put more data in the queue. Inside put( ), wait( ) suspends execution until the Consumer has removed the item from the queue. When execution resumes, the next item of data is put in the queue, and notify( ) is called. This tells the Consumer that it should now remove it.

Output

Put: 1
Got: 1
Put: 2
Got: 2
Put: 3

Got: 3
Put: 4
Got: 4
Put: 5
Got: 5

Leave a Comment