Java is a powerful and versatile programming language that is widely used for developing a wide range of applications. When working with Java, you'll often encounter situations where you need to pause the execution of a thread or wait for a specific event to occur. This is where the concept of waiting in Java comes into play.
Why Do We Need to Wait in Java?
Imagine you're building a web application that retrieves data from a database. Before displaying this data to the user, you need to ensure that the database request has been completed successfully. This is where waiting becomes crucial.
In such scenarios, we use wait() and notify() methods, which are part of the Object class in Java. These methods allow threads to communicate and synchronize their execution.
Understanding wait() and notify()
- wait(): This method causes the current thread to wait until another thread calls notify() or notifyAll() on the same object. When a thread calls wait(), it releases its lock on the object and enters the wait set of the object.
- notify(): This method wakes up a single thread that is waiting on the object. If multiple threads are waiting, the JVM selects one thread randomly to be notified.
- notifyAll(): This method wakes up all threads that are waiting on the object.
How to Use wait() and notify()
Let's illustrate the use of wait() and notify() with a simple example:
public class ProducerConsumer {
private int data;
private boolean dataReady = false;
public synchronized void produce(int data) {
while (dataReady) {
try {
wait(); // Wait if data is already ready
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.data = data;
dataReady = true;
notify(); // Notify the consumer
}
public synchronized int consume() {
while (!dataReady) {
try {
wait(); // Wait if data is not ready
} catch (InterruptedException e) {
e.printStackTrace();
}
}
dataReady = false;
notify(); // Notify the producer
return data;
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
pc.produce(i);
System.out.println("Producer produced: " + i);
try {
Thread.sleep(1000); // Simulate production time
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
int consumedData = pc.consume();
System.out.println("Consumer consumed: " + consumedData);
try {
Thread.sleep(1000); // Simulate consumption time
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
In this example, we have a Producer and a Consumer thread. The Producer produces data (integers) and the Consumer consumes it. The wait() and notify() methods are used to synchronize the threads, ensuring that the Consumer waits for the Producer to produce data before consuming it.
Key Points to Remember
- Synchronization: wait() and notify() methods are used within synchronized blocks or methods. This ensures that only one thread can access the shared resource (the object on which wait() is called) at a time.
- InterruptedException: If a thread is interrupted while waiting, it will throw an InterruptedException. You should handle this exception appropriately.
- Object Locks: The wait() method releases the object lock, allowing other threads to acquire the lock and potentially call notify() or notifyAll().
Alternatives to wait() and notify()
While wait() and notify() are powerful tools for thread synchronization, there are alternative approaches, such as:
- Condition Variables: These provide a more structured way to wait for specific conditions to be met. They are introduced in Java 5.
- BlockingQueue: These are thread-safe data structures that allow producers to add items and consumers to remove items. They provide methods for waiting and notifying.
Conclusion
Understanding how to wait in Java is essential for building robust and efficient multithreaded applications. By using wait() and notify() or their alternatives, you can ensure that your threads communicate and synchronize properly, leading to cleaner and more manageable code.