Inner Thread Communication in Java

Inner thread communication in Java refers to the process by which threads communicate with each other within a single Java application. This can be useful in a variety of situations, such as when one thread needs to wait for another thread to complete a task before it can proceed, or when two threads need to exchange data.

There are several ways to achieve inner thread communication in Java. One common approach is to use the wait() and notify() methods, which are provided as part of the java.lang.Object class. These methods allow a thread to pause its execution and wait for another thread to send a notification that it can continue.

Using wait() and notify() method

To use the wait() and notify() methods, a thread must first acquire the lock on an object. These methods are inherited from Object class. This is done using the synchronized keyword, which ensures that only one thread can execute a block of code at a time. Once a thread has acquired the lock, it can call the wait() method to pause its execution and release the lock. Another thread can then acquire the lock and call the notify() method to wake up the first thread.

Following is an example of how the wait() and notify() methods can be used to synchronize two threads:


public class WaitNotifyDemo {
	public static void main(String[] args) {
		final Object lock = new Object();

		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				synchronized (lock) {
					System.out.println("Thread 1: Holding lock");
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("Thread 1: Lock released");
				}
			}
		});

		Thread thread2 = new Thread(new Runnable() {
			public void run() {
				synchronized (lock) {
					System.out.println("Thread 2: Holding lock");
					lock.notify();
					System.out.println("Thread 2: Notifying");
				}
			}
		});

		thread1.start();
		thread2.start();
	}
}

Output:

Thread 1: Holding lock
Thread 2: Holding lock
Thread 2: Notifying
Thread 1: Lock released

In this example, thread 1 acquires the lock and calls the wait() method, causing it to pause its execution. Thread 2 then acquires the lock and calls the notify() method, waking up thread 1.

Using CountDownLatch class

Another way to achieve inner thread communication in Java is to use the java.util.concurrent package, which provides a number of classes and interfaces that can be used to synchronize threads and exchange data. One such class is the java.util.concurrent.CountDownLatch class, which allows one or more threads to wait for a set of operations to complete.

Following is an example of how the CountDownLatch class can be used to synchronize two threads:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
	public static void main(String[] args) {
		final CountDownLatch latch = new CountDownLatch(1);

		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				System.out.println("Thread 1: Doing some work");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				latch.countDown();
				System.out.println("Thread 1: Work completed");
			}
		});

		Thread thread2 = new Thread(new Runnable() {
			public void run() {
				try {
					System.out.println("Thread 2: Waiting for work to complete");
					latch.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("Thread 2: Work completed");
			}
		});

		thread1.start();
		thread2.start();
	}
}

Output:

Thread 1: Doing some work
Thread 2: Waiting for work to complete
Thread 1: Work completed
Thread 2: Work completed

In this example, thread 1 does some work and then calls the countDown() method on the latch object, decrementing the countdown by 1. Thread 2 calls the await() method on the latch object, causing it to wait until the countdown reaches 0. When this happens, thread 2 continues its execution.

Using BlockingQueye interface

One more way to achieve inner thread communication in Java is through the use of the java.util.concurrent.BlockingQueue interface, which provides a thread-safe queue that can be used to exchange data between threads. A thread can use the put() method to add an element to the queue, and another thread can use the take() method to retrieve an element from the queue.

Let’s an example of how the BlockingQueue interface can be used to synchronize two threads:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
	public static void main(String[] args) {
		final BlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				try {
					System.out.println("Thread 1: Adding element to queue");
					queue.put("Element");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});

		Thread thread2 = new Thread(new Runnable() {
			public void run() {
				try {
					System.out.println("Thread 2: Waiting for element");
					String element = queue.take();
					System.out.println("Thread 2: Element received: " + element);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});

		thread1.start();
		thread2.start();
	}
}

Output:

Thread 2: Waiting for element
Thread 1: Adding element to queue
Thread 2: Element received: Element

In this example, thread 1 adds an element to the queue, and thread 2 waits for an element to be available. When the element is added to the queue, thread 2 retrieves it and continues its execution.

Note: The output you can see here can be different than what you see in your code. This is because two threads are running and there is no guarantee which thread executes first.

Conclusion

Inner thread communication in Java is an important aspect of concurrent programming, and there are a variety of ways to achieve it, including the use of the wait() and notify() methods, the java.util.concurrent.CountDownLatch class, and the BlockingQueue interface. Understanding these different approaches can help us write efficient and reliable multi-threaded applications.