notifyAll()
notifyAll() wakes all threads currently in the object's wait set — every thread that has called wait() on that object and has not yet been woken. Like notify(), it must be called from within a synchronized block or method on the same object, and it throws IllegalMonitorStateException otherwise. Unlike notify(), which wakes an arbitrary single thread, notifyAll() wakes every waiter, but only one of them can reacquire the monitor at a time — they compete like any threads competing for a lock. Each woken thread re-checks its while condition; threads whose condition is still false return to the wait set. This all-wake-then-recheck behavior is the safe default when multiple conditions share a wait set, when multiple threads wait for heterogeneous signals, or when you cannot guarantee that notify() will wake a thread that can actually make progress. This entry covers the full notifyAll() contract, when it is the correct choice over notify(), the thundering herd problem and its performance cost, and when to upgrade from notifyAll() to Condition.signalAll().
notifyAll() Contract — All Waiters Wake and Compete
// ── notifyAll() wakes all — each re-checks its condition ─────────────
public class EventBus {
private String event = null;
public synchronized void publish(String newEvent) {
event = newEvent;
notifyAll(); // ALL subscribers wake and read the event
}
public synchronized String waitForEvent() throws InterruptedException {
while (event == null) wait();
return event; // all threads that woke up return the same event
}
}
EventBus bus = new EventBus();
// Start 5 subscriber threads:
for (int i = 0; i < 5; i++) {
final int id = i;
new Thread(() -> {
try {
String e = bus.waitForEvent();
System.out.println("Subscriber " + id + " received: " + e);
} catch (InterruptedException ex) { Thread.currentThread().interrupt(); }
}).start();
}
Thread.sleep(100);
bus.publish("MARKET_OPEN");
// All 5 subscribers wake, check event != null, proceed
// Output (order varies): each subscriber receives MARKET_OPEN
// ── notifyAll() safe with multiple conditions ─────────────────────────
// When producers and consumers share one wait set, notifyAll() is safe:
public class SharedBuffer<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
public SharedBuffer(int capacity) { this.capacity = capacity; }
public synchronized void put(T item) throws InterruptedException {
while (queue.size() == capacity) wait(); // producers wait here
queue.add(item);
notifyAll(); // safe: producers re-check (full? wait); consumers proceed
}
public synchronized T take() throws InterruptedException {
while (queue.isEmpty()) wait(); // consumers wait here
T item = queue.poll();
notifyAll(); // safe: consumers re-check (empty? wait); producers proceed
return item;
}
}
// ── Thundering herd: 100 threads woken, 1 can proceed ─────────────────
public class ThunderingHerdDemo {
private int tickets = 0;
public synchronized void addTicket() {
tickets++;
notifyAll(); // ALL 100 waiting threads wake up — 99 find no ticket and re-sleep
}
public synchronized int takeTicket() throws InterruptedException {
while (tickets == 0) wait(); // 99 threads return here after notifyAll()
return tickets--;
}
}
// 100 threads waiting:
ThunderingHerdDemo demo = new ThunderingHerdDemo();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try { demo.takeTicket(); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}).start();
}
// One ticket added:
demo.addTicket();
// 100 threads wake, compete for lock, 99 re-sleep — 100 context switches for 1 actual action
// ── notifyAll() on empty wait set — no-op ────────────────────────────
Object obj = new Object();
synchronized (obj) {
obj.notifyAll(); // silently ignored — nobody is waiting
System.out.println("notifyAll on empty wait set: no effect");
}
// ── Comparing behavior: notify vs notifyAll ───────────────────────────
//
// notify() notifyAll()
// Threads woken: exactly 1 all in wait set
// Choice of thread: arbitrary (JVM) all, then compete
// Safe when: one condition, or always
// all waiters identical
// Risk: missed wakeup if thundering herd (performance)
// wrong thread woken
// Default choice: no yes — safer
// Upgrade path: use Condition.signal() use Condition.signalAll()notifyAll() Performance, Thundering Herd, and Condition.signalAll()
// ── Condition.signalAll() — precise, per-condition wakeup ────────────
import java.util.concurrent.locks.*;
public class HighThroughputBuffer<T> {
private final Object[] data;
private int head, tail, count;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
@SuppressWarnings("unchecked")
public HighThroughputBuffer(int capacity) {
data = new Object[capacity];
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == data.length) notFull.await(); // only producers here
data[tail] = item;
tail = (tail + 1) % data.length;
count++;
notEmpty.signal(); // wake exactly ONE consumer — no herd
} finally { lock.unlock(); }
}
@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) notEmpty.await(); // only consumers here
T item = (T) data[head];
data[head] = null;
head = (head + 1) % data.length;
count--;
notFull.signal(); // wake exactly ONE producer — no herd
return item;
} finally { lock.unlock(); }
}
}
// ── Performance comparison: notifyAll vs Condition.signal ─────────────
// Scenario: 50 producers, 50 consumers, buffer capacity 10
// With Object.notifyAll():
// Each put() call: 50 threads wake (50 wasted context switches per operation)
// Each take() call: 50 threads wake (50 wasted context switches per operation)
// Total overhead per item: ~100 unnecessary context switches
// With Condition.signal():
// Each put() call: 1 consumer wakes (0 wasted context switches)
// Each take() call: 1 producer wakes (0 wasted context switches)
// Total overhead per item: 0 unnecessary context switches
// For 1,000,000 items:
// notifyAll(): ~100,000,000 unnecessary context switches (~100x overhead)
// signal(): ~0 unnecessary context switches
// ── signalAll() for broadcast scenarios ──────────────────────────────
// Even with Condition, signalAll() is sometimes the right choice:
public class StartingGun {
private boolean fired = false;
private final ReentrantLock lock = new ReentrantLock();
private final Condition ready = lock.newCondition();
public void fire() {
lock.lock();
try {
fired = true;
ready.signalAll(); // ALL waiting threads should start — broadcast correct here
} finally { lock.unlock(); }
}
public void awaitStart() throws InterruptedException {
lock.lock();
try {
while (!fired) ready.await();
} finally { lock.unlock(); }
}
}
StartingGun gun = new StartingGun();
for (int i = 0; i < 10; i++) {
final int id = i;
new Thread(() -> {
try {
gun.awaitStart();
System.out.println("Runner " + id + " started!");
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}).start();
}
Thread.sleep(500);
gun.fire(); // all 10 runners start simultaneously — signalAll() is correct here
// ── Decision guide: notify vs notifyAll vs Condition ─────────────────
// 1. Only one condition, interchangeable waiters:
// → notify() is safe and efficient
// 2. Multiple conditions on same lock, or heterogeneous waiters:
// → notifyAll() for correctness; accept thundering herd
// 3. Multiple conditions, high throughput required:
// → Condition.signal() with separate notFull/notEmpty conditions
// 4. Broadcast to all waiters (startup, event publication):
// → notifyAll() or Condition.signalAll() — both are correct here
// 5. Complex concurrent data structure in production:
// → Use java.util.concurrent (BlockingQueue, etc.) — pre-optimized