Wait, Notify, and NotifyAll in Java

Table of Contents

  1. Introduction to Wait/Notify
  2. The Wait Method
  3. The Notify Method
  4. The NotifyAll Method
  5. Producer-Consumer Pattern
  6. Common Pitfalls and Solutions
  7. Best Practices
  8. Advanced Examples
  9. Complete Working Examples

Introduction to Wait/Notify

The wait(), notify(), and notifyAll() methods are used for inter-thread communication in Java. They allow threads to coordinate their activities and avoid busy waiting.

Key Points:

  • Defined in java.lang.Object class
  • Must be called from synchronized context
  • Used for efficient thread coordination
  • Alternative to polling and busy waiting

Basic Concept:

public class BasicWaitNotify {
private final Object lock = new Object();
private boolean condition = false;
public void waitForCondition() throws InterruptedException {
synchronized (lock) {
while (!condition) {
lock.wait(); // Releases lock and waits
}
// Condition is true, proceed with work
System.out.println("Condition met, continuing...");
}
}
public void setCondition() {
synchronized (lock) {
condition = true;
lock.notify(); // Notifies one waiting thread
}
}
}

The Wait Method

wait() Method Characteristics:

  • Causes current thread to wait until another thread invokes notify() or notifyAll()
  • Releases the monitor lock while waiting
  • Can throw InterruptedException
  • Should always be called in a loop checking condition

wait() Variants:

public class WaitMethods {
private final Object lock = new Object();
public void demonstrateWaits() throws InterruptedException {
synchronized (lock) {
// 1. wait() - waits indefinitely
System.out.println("Waiting indefinitely...");
lock.wait(); // Wait until notified
// 2. wait(long timeout) - waits with timeout
System.out.println("Waiting with timeout...");
lock.wait(5000); // Wait up to 5 seconds
// 3. wait(long timeout, int nanos) - more precise timeout
System.out.println("Waiting with precise timeout...");
lock.wait(5000, 500000); // 5.5 seconds total
}
}
}

Proper Wait Pattern:

public class ProperWaitPattern {
private final Object lock = new Object();
private boolean resourceAvailable = false;
private String sharedResource;
// Consumer thread method
public String consumeResource() throws InterruptedException {
synchronized (lock) {
// ALWAYS use wait in a loop with condition check
while (!resourceAvailable) {
System.out.println(Thread.currentThread().getName() + " waiting for resource...");
lock.wait();
}
// Resource is available
resourceAvailable = false;
String result = sharedResource;
sharedResource = null;
System.out.println(Thread.currentThread().getName() + " consumed: " + result);
return result;
}
}
// Producer thread method
public void produceResource(String resource) {
synchronized (lock) {
// Wait if resource is still available (buffer full)
while (resourceAvailable) {
try {
System.out.println(Thread.currentThread().getName() + " waiting to produce...");
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// Produce resource
sharedResource = resource;
resourceAvailable = true;
System.out.println(Thread.currentThread().getName() + " produced: " + resource);
lock.notifyAll(); // Notify all waiting consumers
}
}
public static void main(String[] args) throws InterruptedException {
ProperWaitPattern example = new ProperWaitPattern();
Thread producer = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
example.produceResource("Resource-" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "Producer");
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
try {
example.consumeResource();
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "Consumer");
producer.start();
consumer.start();
producer.join();
consumer.join();
}
}

Output:

Producer produced: Resource-1
Consumer consumed: Resource-1
Producer produced: Resource-2
Consumer consumed: Resource-2
Producer produced: Resource-3
Consumer consumed: Resource-3

The Notify Method

notify() Method Characteristics:

  • Wakes up one randomly selected thread waiting on the same object
  • The awakened thread must reacquire the monitor lock before proceeding
  • Does not release the lock immediately

Single Notification Example:

public class NotifyExample {
private final Object lock = new Object();
private int taskCount = 0;
private final int MAX_TASKS = 5;
public void worker(String name) throws InterruptedException {
synchronized (lock) {
while (taskCount >= MAX_TASKS) {
System.out.println(name + " waiting - too many tasks");
lock.wait();
}
taskCount++;
System.out.println(name + " started task. Active tasks: " + taskCount);
// Simulate work
Thread.sleep(1000);
taskCount--;
System.out.println(name + " completed task. Active tasks: " + taskCount);
// Notify one waiting worker
lock.notify();
}
}
public static void main(String[] args) {
NotifyExample example = new NotifyExample();
// Create multiple worker threads
for (int i = 1; i <= 3; i++) {
final int workerId = i;
new Thread(() -> {
try {
for (int j = 0; j < 3; j++) {
example.worker("Worker-" + workerId);
Thread.sleep(500);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
}

Output:

Worker-1 started task. Active tasks: 1
Worker-2 started task. Active tasks: 2
Worker-3 started task. Active tasks: 3
Worker-1 completed task. Active tasks: 2
Worker-2 started task. Active tasks: 3
Worker-2 completed task. Active tasks: 2
Worker-3 started task. Active tasks: 3
Worker-3 completed task. Active tasks: 2
Worker-1 started task. Active tasks: 3
...

The NotifyAll Method

notifyAll() Method Characteristics:

  • Wakes up all threads waiting on the same object
  • All awakened threads compete for the lock
  • Useful when multiple threads are waiting for different conditions

notifyAll() vs notify():

public class NotifyAllExample {
private final Object lock = new Object();
private boolean condition1 = false;
private boolean condition2 = false;
public void waitForCondition1() throws InterruptedException {
synchronized (lock) {
while (!condition1) {
System.out.println(Thread.currentThread().getName() + " waiting for condition1");
lock.wait();
}
System.out.println(Thread.currentThread().getName() + " condition1 satisfied");
condition1 = false; // Reset condition
}
}
public void waitForCondition2() throws InterruptedException {
synchronized (lock) {
while (!condition2) {
System.out.println(Thread.currentThread().getName() + " waiting for condition2");
lock.wait();
}
System.out.println(Thread.currentThread().getName() + " condition2 satisfied");
condition2 = false; // Reset condition
}
}
public void setCondition1() {
synchronized (lock) {
condition1 = true;
System.out.println("Condition1 set - using notify()");
lock.notify(); // May wake up wrong thread!
}
}
public void setCondition2() {
synchronized (lock) {
condition2 = true;
System.out.println("Condition2 set - using notifyAll()");
lock.notifyAll(); // Wakes all, correct threads will proceed
}
}
public static void main(String[] args) throws InterruptedException {
NotifyAllExample example = new NotifyAllExample();
// Create threads waiting for different conditions
Thread waiter1 = new Thread(() -> {
try {
example.waitForCondition1();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Condition1-Waiter");
Thread waiter2 = new Thread(() -> {
try {
example.waitForCondition2();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Condition2-Waiter");
Thread waiter3 = new Thread(() -> {
try {
example.waitForCondition1();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Condition1-Waiter2");
waiter1.start();
waiter2.start();
waiter3.start();
Thread.sleep(1000);
// Problem: notify() might wake up wrong thread
example.setCondition1(); // Uses notify() - may wake Condition2-Waiter
Thread.sleep(2000);
// Solution: notifyAll() ensures correct threads wake up
example.setCondition2(); // Uses notifyAll()
waiter1.join(1000);
waiter2.join(1000);
waiter3.join(1000);
}
}

Producer-Consumer Pattern

Bounded Buffer Implementation:

public class BoundedBuffer<T> {
private final T[] buffer;
private int count = 0;
private int putIndex = 0;
private int takeIndex = 0;
private final Object lock = new Object();
@SuppressWarnings("unchecked")
public BoundedBuffer(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("Capacity must be positive");
}
this.buffer = (T[]) new Object[capacity];
}
public void put(T item) throws InterruptedException {
synchronized (lock) {
// Wait until buffer has space
while (count == buffer.length) {
System.out.println(Thread.currentThread().getName() + " waiting to put - buffer full");
lock.wait();
}
// Add item to buffer
buffer[putIndex] = item;
putIndex = (putIndex + 1) % buffer.length;
count++;
System.out.println(Thread.currentThread().getName() + " put: " + item + 
" (count: " + count + ")");
// Notify waiting consumers
lock.notifyAll();
}
}
public T take() throws InterruptedException {
synchronized (lock) {
// Wait until buffer has items
while (count == 0) {
System.out.println(Thread.currentThread().getName() + " waiting to take - buffer empty");
lock.wait();
}
// Remove item from buffer
T item = buffer[takeIndex];
buffer[takeIndex] = null; // Help GC
takeIndex = (takeIndex + 1) % buffer.length;
count--;
System.out.println(Thread.currentThread().getName() + " took: " + item + 
" (count: " + count + ")");
// Notify waiting producers
lock.notifyAll();
return item;
}
}
public int size() {
synchronized (lock) {
return count;
}
}
public boolean isEmpty() {
synchronized (lock) {
return count == 0;
}
}
public boolean isFull() {
synchronized (lock) {
return count == buffer.length;
}
}
}
// Test the bounded buffer
public class BoundedBufferTest {
public static void main(String[] args) throws InterruptedException {
BoundedBuffer<String> buffer = new BoundedBuffer<>(3);
Thread producer1 = new Thread(new Producer(buffer, "Producer-1"));
Thread producer2 = new Thread(new Producer(buffer, "Producer-2"));
Thread consumer1 = new Thread(new Consumer(buffer, "Consumer-1"));
Thread consumer2 = new Thread(new Consumer(buffer, "Consumer-2"));
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
// Run for 10 seconds
Thread.sleep(10000);
producer1.interrupt();
producer2.interrupt();
consumer1.interrupt();
consumer2.interrupt();
producer1.join();
producer2.join();
consumer1.join();
consumer2.join();
System.out.println("Final buffer size: " + buffer.size());
}
}
class Producer implements Runnable {
private final BoundedBuffer<String> buffer;
private final String name;
private int itemCount = 0;
public Producer(BoundedBuffer<String> buffer, String name) {
this.buffer = buffer;
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
while (!Thread.currentThread().isInterrupted()) {
try {
String item = name + "-Item-" + (++itemCount);
buffer.put(item);
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println(name + " stopped");
}
}
class Consumer implements Runnable {
private final BoundedBuffer<String> buffer;
private final String name;
public Consumer(BoundedBuffer<String> buffer, String name) {
this.buffer = buffer;
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
while (!Thread.currentThread().isInterrupted()) {
try {
String item = buffer.take();
// Process the item
Thread.sleep((long) (Math.random() * 1500));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println(name + " stopped");
}
}

Common Pitfalls and Solutions

1. Spurious Wakeups:

public class SpuriousWakeupProtection {
private final Object lock = new Object();
private boolean condition = false;
// WRONG: No loop to check condition
public void wrongWait() throws InterruptedException {
synchronized (lock) {
if (!condition) {
lock.wait(); // May wake up without notify!
}
// Proceed assuming condition is true (DANGEROUS)
System.out.println("Proceeding (may be wrong!)");
}
}
// CORRECT: Always use wait in a loop
public void correctWait() throws InterruptedException {
synchronized (lock) {
while (!condition) {
lock.wait(); // Safe from spurious wakeups
}
// Condition is definitely true
System.out.println("Proceeding (condition verified)");
}
}
public void setCondition() {
synchronized (lock) {
condition = true;
lock.notifyAll();
}
}
}

2. Lost Notification:

public class LostNotificationExample {
private final Object lock = new Object();
private boolean ready = false;
// WRONG: Notification can be lost
public void consumerWrong() throws InterruptedException {
synchronized (lock) {
if (!ready) {
lock.wait(); // May miss notify() called before wait()
}
System.out.println("Consumer proceeding");
}
}
public void producerWrong() {
synchronized (lock) {
ready = true;
lock.notify(); // May be called before consumer starts waiting
}
}
// CORRECT: Proper coordination
private volatile boolean producerStarted = false;
public void consumerCorrect() throws InterruptedException {
synchronized (lock) {
// Wait for producer to start and set condition
while (!ready) {
if (!producerStarted) {
// Start producer if not already started
startProducer();
}
lock.wait();
}
System.out.println("Consumer proceeding correctly");
}
}
public void producerCorrect() {
synchronized (lock) {
producerStarted = true;
ready = true;
lock.notifyAll();
}
}
private void startProducer() {
new Thread(this::producerCorrect).start();
}
}

3. IllegalMonitorStateException:

public class MonitorStateExample {
private final Object lock = new Object();
// WRONG: Calling wait/notify without synchronization
public void wrongMethod() throws InterruptedException {
// lock.wait(); // Throws IllegalMonitorStateException
// lock.notify(); // Throws IllegalMonitorStateException
}
// CORRECT: Must be in synchronized block
public void correctMethod() throws InterruptedException {
synchronized (lock) {
lock.wait(); // OK - current thread owns the lock
lock.notify(); // OK - current thread owns the lock
}
}
// WRONG: Synchronizing on different object
public void wrongSynchronization() throws InterruptedException {
Object differentLock = new Object();
synchronized (lock) {
// differentLock.wait(); // Throws IllegalMonitorStateException
}
}
}

Best Practices

1. Always Use Wait in Loop:

public class BestPracticeWait {
private final Object lock = new Object();
private boolean condition = false;
// ✅ GOOD
public void goodWait() throws InterruptedException {
synchronized (lock) {
while (!condition) {
lock.wait();
}
// Condition is definitely true
}
}
// ❌ BAD
public void badWait() throws InterruptedException {
synchronized (lock) {
if (!condition) {
lock.wait();
}
// Condition might not be true (spurious wakeup)
}
}
}

2. Prefer notifyAll() Over notify():

public class NotifyBestPractice {
private final Object lock = new Object();
private boolean condition1 = false;
private boolean condition2 = false;
// ✅ GOOD - use notifyAll() when multiple conditions exist
public void goodNotification() {
synchronized (lock) {
condition1 = true;
condition2 = true;
lock.notifyAll(); // All waiting threads get chance
}
}
// ⚠️ USE WITH CAUTION - notify() can be more efficient but risky
public void cautiousNotification() {
synchronized (lock) {
condition1 = true;
lock.notify(); // Only use when you know exactly which thread to wake
}
}
}

3. Use Timeout with Wait:

public class WaitWithTimeout {
private final Object lock = new Object();
private boolean condition = false;
public boolean waitWithTimeout(long timeoutMs) throws InterruptedException {
synchronized (lock) {
long startTime = System.currentTimeMillis();
long remaining = timeoutMs;
while (!condition && remaining > 0) {
lock.wait(remaining);
remaining = timeoutMs - (System.currentTimeMillis() - startTime);
}
return condition; // Return whether condition was met
}
}
public void setCondition() {
synchronized (lock) {
condition = true;
lock.notifyAll();
}
}
}

Advanced Examples

1. Read-Write Lock with Wait/Notify:

public class ReadWriteLock {
private int readers = 0;
private int writers = 0;
private int writeRequests = 0;
private final Object lock = new Object();
public void readLock() throws InterruptedException {
synchronized (lock) {
while (writers > 0 || writeRequests > 0) {
lock.wait();
}
readers++;
}
}
public void readUnlock() {
synchronized (lock) {
readers--;
if (readers == 0) {
lock.notifyAll(); // Notify waiting writers
}
}
}
public void writeLock() throws InterruptedException {
synchronized (lock) {
writeRequests++;
while (readers > 0 || writers > 0) {
lock.wait();
}
writeRequests--;
writers++;
}
}
public void writeUnlock() {
synchronized (lock) {
writers--;
lock.notifyAll(); // Notify all waiting readers and writers
}
}
}

2. CountDown Latch Implementation:

public class SimpleCountDownLatch {
private int count;
private final Object lock = new Object();
public SimpleCountDownLatch(int count) {
if (count < 0) {
throw new IllegalArgumentException("Count cannot be negative");
}
this.count = count;
}
public void await() throws InterruptedException {
synchronized (lock) {
while (count > 0) {
lock.wait();
}
}
}
public void countDown() {
synchronized (lock) {
if (count > 0) {
count--;
if (count == 0) {
lock.notifyAll(); // Notify all waiting threads
}
}
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}

3. Connection Pool with Wait/Notify:

import java.util.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionPool {
private final LinkedList<Connection> availableConnections = new LinkedList<>();
private final LinkedList<Connection> usedConnections = new LinkedList<>();
private final int maxSize;
private final Object lock = new Object();
public ConnectionPool(String url, String user, String password, int maxSize) 
throws SQLException {
this.maxSize = maxSize;
// Initialize connections
for (int i = 0; i < maxSize / 2; i++) {
availableConnections.add(createConnection(url, user, password));
}
}
public Connection getConnection() throws InterruptedException, SQLException {
synchronized (lock) {
// Wait until a connection is available or we can create a new one
while (availableConnections.isEmpty() && 
usedConnections.size() >= maxSize) {
System.out.println(Thread.currentThread().getName() + " waiting for connection");
lock.wait();
}
Connection connection;
if (!availableConnections.isEmpty()) {
connection = availableConnections.removeFirst();
} else {
connection = createConnection("jdbc:mysql://localhost:3306/test", "user", "password");
}
usedConnections.add(connection);
System.out.println(Thread.currentThread().getName() + " got connection. " +
"Available: " + availableConnections.size() +
", Used: " + usedConnections.size());
return new PooledConnection(connection);
}
}
private Connection createConnection(String url, String user, String password) 
throws SQLException {
// In real implementation, use actual connection creation
// return DriverManager.getConnection(url, user, password);
return new MockConnection();
}
private void returnConnection(Connection connection) {
synchronized (lock) {
if (usedConnections.remove(connection)) {
availableConnections.add(connection);
System.out.println(Thread.currentThread().getName() + " returned connection. " +
"Available: " + availableConnections.size() +
", Used: " + usedConnections.size());
lock.notifyAll(); // Notify waiting threads
}
}
}
// Mock connection class for demonstration
private static class MockConnection implements Connection {
// Implement Connection methods (omitted for brevity)
public void close() throws SQLException {}
public boolean isClosed() throws SQLException { return false; }
// ... other Connection methods
}
// Wrapper class to intercept close() calls
private class PooledConnection implements Connection {
private final Connection realConnection;
public PooledConnection(Connection realConnection) {
this.realConnection = realConnection;
}
@Override
public void close() throws SQLException {
returnConnection(realConnection);
}
@Override
public boolean isClosed() throws SQLException {
return realConnection.isClosed();
}
// Delegate all other Connection methods to realConnection
// ... implementation omitted for brevity
}
}

Complete Working Examples

Complete Thread Coordination System:

import java.util.*;
import java.util.concurrent.*;
public class CompleteWaitNotifyExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Complete Wait/Notify Example ===\n");
// Example 1: Basic Wait/Notify
basicWaitNotifyExample();
// Example 2: Producer-Consumer with Multiple Conditions
producerConsumerMultipleConditions();
// Example 3: Worker Thread Pool
workerThreadPoolExample();
// Example 4: Resource Pool with Timeout
resourcePoolWithTimeoutExample();
System.out.println("\n=== All Examples Completed ===");
}
private static void basicWaitNotifyExample() throws InterruptedException {
System.out.println("1. Basic Wait/Notify Example:");
Signal signal = new Signal();
Thread waiter = new Thread(() -> {
try {
System.out.println("Waiter thread started");
signal.waitForSignal();
System.out.println("Waiter thread received signal");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Waiter");
Thread notifier = new Thread(() -> {
try {
System.out.println("Notifier thread started");
Thread.sleep(2000); // Simulate work
signal.sendSignal();
System.out.println("Notifier thread sent signal");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Notifier");
waiter.start();
notifier.start();
waiter.join();
notifier.join();
}
private static void producerConsumerMultipleConditions() throws InterruptedException {
System.out.println("\n2. Producer-Consumer with Multiple Conditions:");
MultiConditionBuffer buffer = new MultiConditionBuffer(2);
Thread producer1 = new Thread(new MultiConditionProducer(buffer, "FastProducer"), "FastProducer");
Thread producer2 = new Thread(new MultiConditionProducer(buffer, "SlowProducer"), "SlowProducer");
Thread consumer1 = new Thread(new MultiConditionConsumer(buffer, "FastConsumer"), "FastConsumer");
Thread consumer2 = new Thread(new MultiConditionConsumer(buffer, "SlowConsumer"), "SlowConsumer");
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
Thread.sleep(5000);
producer1.interrupt();
producer2.interrupt();
consumer1.interrupt();
consumer2.interrupt();
producer1.join(1000);
producer2.join(1000);
consumer1.join(1000);
consumer2.join(1000);
}
private static void workerThreadPoolExample() throws InterruptedException {
System.out.println("\n3. Worker Thread Pool Example:");
WorkerPool pool = new WorkerPool(2, 5);
// Submit tasks
for (int i = 1; i <= 10; i++) {
final int taskId = i;
pool.submit(() -> {
System.out.println(Thread.currentThread().getName() + " executing task " + taskId);
try {
Thread.sleep(500); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " completed task " + taskId);
});
}
Thread.sleep(4000);
pool.shutdown();
}
private static void resourcePoolWithTimeoutExample() throws InterruptedException {
System.out.println("\n4. Resource Pool with Timeout Example:");
TimeoutResourcePool pool = new TimeoutResourcePool(2);
List<Thread> threads = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
final int threadId = i;
Thread thread = new Thread(() -> {
try {
System.out.println("Thread " + threadId + " requesting resource");
String resource = pool.acquire(2000); // 2 second timeout
if (resource != null) {
System.out.println("Thread " + threadId + " acquired: " + resource);
Thread.sleep(1000); // Use resource
pool.release(resource);
System.out.println("Thread " + threadId + " released: " + resource);
} else {
System.out.println("Thread " + threadId + " timed out waiting for resource");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
}
}
// Supporting classes for the examples
class Signal {
private final Object lock = new Object();
private boolean signaled = false;
public void waitForSignal() throws InterruptedException {
synchronized (lock) {
while (!signaled) {
System.out.println(Thread.currentThread().getName() + " waiting for signal...");
lock.wait();
}
signaled = false; // Reset for next use
}
}
public void sendSignal() {
synchronized (lock) {
signaled = true;
lock.notify();
}
}
}
class MultiConditionBuffer {
private final Queue<String> buffer;
private final int capacity;
private final Object lock = new Object();
private int consumerCount = 0;
private final int MAX_CONSUMERS = 2;
public MultiConditionBuffer(int capacity) {
this.capacity = capacity;
this.buffer = new LinkedList<>();
}
public void produce(String item) throws InterruptedException {
synchronized (lock) {
// Wait if buffer is full
while (buffer.size() >= capacity) {
System.out.println(Thread.currentThread().getName() + " waiting - buffer full");
lock.wait();
}
buffer.offer(item);
System.out.println(Thread.currentThread().getName() + " produced: " + item + 
" (size: " + buffer.size() + ")");
// Notify all waiting consumers
lock.notifyAll();
}
}
public String consume() throws InterruptedException {
synchronized (lock) {
// Wait if buffer is empty or too many consumers
while (buffer.isEmpty() || consumerCount >= MAX_CONSUMERS) {
if (consumerCount >= MAX_CONSUMERS) {
System.out.println(Thread.currentThread().getName() + " waiting - too many consumers");
} else {
System.out.println(Thread.currentThread().getName() + " waiting - buffer empty");
}
lock.wait();
}
consumerCount++;
try {
String item = buffer.poll();
System.out.println(Thread.currentThread().getName() + " consuming: " + item + 
" (size: " + buffer.size() + ", consumers: " + consumerCount + ")");
// Simulate consumption time
Thread.sleep(1000);
return item;
} finally {
consumerCount--;
// Notify waiting producers and consumers
lock.notifyAll();
}
}
}
}
class MultiConditionProducer implements Runnable {
private final MultiConditionBuffer buffer;
private final String name;
public MultiConditionProducer(MultiConditionBuffer buffer, String name) {
this.buffer = buffer;
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
int itemCount = 0;
while (!Thread.currentThread().isInterrupted()) {
try {
String item = name + "-Item-" + (++itemCount);
buffer.produce(item);
Thread.sleep(800);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println(name + " stopped");
}
}
class MultiConditionConsumer implements Runnable {
private final MultiConditionBuffer buffer;
private final String name;
public MultiConditionConsumer(MultiConditionBuffer buffer, String name) {
this.buffer = buffer;
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
while (!Thread.currentThread().isInterrupted()) {
try {
String item = buffer.consume();
if (item != null) {
// Item was consumed
}
Thread.sleep(1200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println(name + " stopped");
}
}
class WorkerPool {
private final BlockingQueue<Runnable> taskQueue;
private final List<Worker> workers;
private volatile boolean shutdown = false;
private final Object lock = new Object();
public WorkerPool(int poolSize, int queueSize) {
this.taskQueue = new LinkedBlockingQueue<>(queueSize);
this.workers = new ArrayList<>();
// Create worker threads
for (int i = 0; i < poolSize; i++) {
Worker worker = new Worker("Worker-" + (i + 1));
workers.add(worker);
worker.start();
}
}
public void submit(Runnable task) throws InterruptedException {
synchronized (lock) {
if (shutdown) {
throw new IllegalStateException("Pool is shutdown");
}
while (taskQueue.remainingCapacity() == 0) {
System.out.println("Task queue full, waiting...");
lock.wait();
}
taskQueue.offer(task);
lock.notifyAll(); // Notify waiting workers
}
}
public void shutdown() throws InterruptedException {
synchronized (lock) {
shutdown = true;
lock.notifyAll(); // Notify all workers to exit
}
// Wait for all workers to finish
for (Worker worker : workers) {
worker.join();
}
}
private class Worker extends Thread {
public Worker(String name) {
super(name);
}
@Override
public void run() {
try {
while (!shutdown || !taskQueue.isEmpty()) {
Runnable task;
synchronized (lock) {
while (taskQueue.isEmpty() && !shutdown) {
lock.wait();
}
if (shutdown && taskQueue.isEmpty()) {
break;
}
task = taskQueue.poll();
lock.notifyAll(); // Notify that queue has space
}
if (task != null) {
task.run();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(getName() + " stopped");
}
}
}
class TimeoutResourcePool {
private final Queue<String> availableResources;
private final Set<String> usedResources;
private final int maxSize;
private final Object lock = new Object();
public TimeoutResourcePool(int maxSize) {
this.maxSize = maxSize;
this.availableResources = new LinkedList<>();
this.usedResources = new HashSet<>();
// Initialize resources
for (int i = 1; i <= maxSize; i++) {
availableResources.offer("Resource-" + i);
}
}
public String acquire(long timeoutMs) throws InterruptedException {
synchronized (lock) {
long startTime = System.currentTimeMillis();
long remaining = timeoutMs;
while (availableResources.isEmpty() && remaining > 0) {
lock.wait(remaining);
remaining = timeoutMs - (System.currentTimeMillis() - startTime);
}
if (!availableResources.isEmpty()) {
String resource = availableResources.poll();
usedResources.add(resource);
return resource;
}
return null; // Timeout
}
}
public void release(String resource) {
synchronized (lock) {
if (usedResources.remove(resource)) {
availableResources.offer(resource);
lock.notifyAll(); // Notify waiting threads
}
}
}
}

Expected Output:

=== Complete Wait/Notify Example ===
1. Basic Wait/Notify Example:
Waiter thread started
Notifier thread started
Waiter thread waiting for signal...
Notifier thread sent signal
Waiter thread received signal
2. Producer-Consumer with Multiple Conditions:
FastProducer produced: FastProducer-Item-1 (size: 1)
SlowProducer produced: SlowProducer-Item-1 (size: 2)
FastConsumer consuming: FastProducer-Item-1 (size: 1, consumers: 1)
SlowConsumer waiting - too many consumers
FastConsumer completed task FastProducer-Item-1
SlowConsumer consuming: SlowProducer-Item-1 (size: 0, consumers: 1)
...
3. Worker Thread Pool Example:
Worker-1 executing task 1
Worker-2 executing task 2
Worker-1 completed task 1
Worker-1 executing task 3
Worker-2 completed task 2
Worker-2 executing task 4
...
4. Resource Pool with Timeout Example:
Thread 1 requesting resource
Thread 2 requesting resource
Thread 3 requesting resource
Thread 1 acquired: Resource-1
Thread 2 acquired: Resource-2
Thread 4 requesting resource
Thread 5 requesting resource
Thread 1 released: Resource-1
Thread 3 acquired: Resource-1
Thread 2 released: Resource-2
Thread 4 acquired: Resource-2
Thread 3 released: Resource-1
Thread 5 acquired: Resource-1
...
=== All Examples Completed ===

Key Takeaways

  1. wait() - Releases lock and waits for notification
  2. notify() - Wakes one waiting thread (random selection)
  3. notifyAll() - Wakes all waiting threads
  4. Always use wait() in a loop to handle spurious wakeups
  5. Synchronization is required before calling wait/notify
  6. Prefer notifyAll() when multiple conditions exist
  7. Consider timeouts to prevent indefinite waiting
  8. Use proper exception handling for InterruptedException

Wait/Notify mechanisms are fundamental for efficient thread coordination in Java, but they require careful implementation to avoid common pitfalls like race conditions, deadlocks, and lost notifications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper