Welcome back to our advanced Java concurrency course! In previous lessons, we explored lock mechanisms and deadlock prevention. Now, we will focus on thread coordination using the wait()
and notify()
methods in Java, a powerful approach to managing inter-thread communication.
In this lesson, you will:
- Learn how to implement thread coordination using the
wait()
andnotify()
methods. - Understand how to synchronize threads for correct execution order.
- Apply countdown logic to real-world scenarios involving thread dependencies.
By the end of this lesson, you’ll be able to use wait()
and notify()
to coordinate thread activities effectively, improving the performance and reliability of your concurrent applications.
Thread coordination involves managing the execution order of threads, ensuring that they wait for specific conditions before continuing their work. The wait()
and notify()
methods allow threads to communicate with each other and synchronize their actions.
In this example, we’ll use a countdown mechanism to coordinate two threads: one that waits for the countdown to finish, and another that performs the countdown and notifies the waiting thread when it’s complete.
Java1public class Countdown { 2 private boolean isReady = false; 3 4 public synchronized void waitForCountdown() throws InterruptedException { 5 while (!isReady) { 6 System.out.println("Waiting for countdown..."); 7 wait(); 8 } 9 System.out.println("Countdown complete! Proceeding..."); 10 } 11 12 public synchronized void startCountdown() throws InterruptedException { 13 System.out.println("Countdown started..."); 14 Thread.sleep(2000); // Simulate countdown time 15 isReady = true; 16 notify(); 17 System.out.println("Countdown finished! Notified waiting thread."); 18 } 19}
In the Countdown
class, we manage a boolean flag isReady
to coordinate two actions: waiting for a countdown and performing the countdown. The wait()
method causes the waiting thread to pause until the countdown completes, while notify()
alerts the waiting thread that it can now proceed.
-
The
wait()
method makes the thread release the lock on the object and pauses its execution until another thread callsnotify()
ornotifyAll()
. In this case, thewaitForCountdown()
method useswait()
to make the thread wait while the countdown is ongoing. -
The
notify()
method wakes up a single waiting thread. It signals that the countdown is complete, allowing the waiting thread to proceed with its task. In thestartCountdown()
method,notify()
is called after the countdown has finished, allowing the waiting thread to resume execution.
By using a boolean flag (isReady
), we ensure that the thread only proceeds after the countdown is finished. This mechanism, using wait()
and notify()
, is essential for coordinating thread actions without wasting resources by having threads check for a condition in a loop.
Both the waitForCountdown()
and startCountdown()
methods throw InterruptedException
because wait()
and Thread.sleep()
can be interrupted by other threads. Handling this exception ensures that if the thread is interrupted while waiting or sleeping, it can react appropriately by restoring the interrupted state or performing necessary cleanup.
Now that we have an understanding of the countdown mechanism, let's see how it is used to coordinate two threads. One thread will wait for the countdown, while the other will perform the countdown and notify the waiting thread.
Java1public class Main { 2 public static void main(String[] args) throws InterruptedException { 3 Countdown countdown = new Countdown(); 4 5 // Thread that waits for the countdown 6 Thread waiter = new Thread(() -> { 7 try { 8 countdown.waitForCountdown(); 9 } catch (InterruptedException e) { 10 Thread.currentThread().interrupt(); 11 } 12 }); 13 14 // Thread that performs the countdown 15 Thread notifier = new Thread(() -> { 16 try { 17 countdown.startCountdown(); 18 } catch (InterruptedException e) { 19 Thread.currentThread().interrupt(); 20 } 21 }); 22 23 waiter.start(); 24 Thread.sleep(1000); // Ensure the waiting thread starts first 25 notifier.start(); 26 27 waiter.join(); 28 notifier.join(); 29 } 30}
In this example:
- Two threads are created: one that waits (
waiter
) and one that performs the countdown (notifier
). - A short delay (
Thread.sleep(1000)
) ensures that thewaiter
starts before the countdown begins, ensuring correct order of execution. - The
join()
method is used to ensure both threads finish before the program terminates, ensuring a clean and controlled shutdown.
Running the above code results in the following output:
1Waiting for countdown... 2Countdown started... 3Countdown finished! Notified waiting thread. 4Countdown complete! Proceeding...
Here’s what happens:
- Thread waiter starts first and immediately calls the
waitForCountdown()
method. SinceisReady
is false, thewait()
method is called, causing the thread to wait. - After a brief delay of 1 second, thread notifier starts and calls the
startCountdown()
method. It simulates a countdown by sleeping for 2 seconds, then setsisReady
to true and callsnotify()
to wake the waiting thread. - Once the waiting thread is notified, it resumes, completing its task and printing "Countdown complete! Proceeding...".
This setup demonstrates the power of thread coordination using wait()
and notify()
. The waiting thread only proceeds when it is signaled, ensuring proper synchronization between the two threads.
Understanding and using wait()
and notify()
is essential for managing inter-thread communication in multithreaded programs. These methods help you control the execution flow of threads, ensuring that threads wait for necessary conditions before proceeding.
Here’s why these concepts are important:
- Efficient Communication:
wait()
andnotify()
allow threads to communicate efficiently, reducing CPU usage by avoiding busy-waiting or continuous polling. - Prevention of Resource Contention: By coordinating thread actions, we prevent race conditions and resource conflicts, ensuring that shared resources are used efficiently and safely.
- Improved System Performance: Proper synchronization improves the overall performance of your application, reducing the chances of deadlock, race conditions, or bottlenecks.
Mastering thread coordination techniques like these enables you to develop robust, high-performance, and scalable multithreaded applications.
Now that you understand how to use wait()
and notify()
for thread coordination, let’s move to the practice section and apply these concepts!