google-site-verification: google61fe8ba583a51912.html
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