wait() and notify() let threads coordinate — sleep until a condition becomes true — instead of busy-spinning. In modern code a BlockingQueue usually does this for you.
The rules
Method (on Object) | Effect |
|---|---|
wait() | Release this object's monitor and wait (state WAITING) until notified or interrupted |
wait(ms) | Same, with a timeout (TIMED_WAITING) |
notify() | Wake one arbitrary waiting thread |
notifyAll() | Wake all waiting threads — they compete for the monitor and re-check their conditions |
All three must be called while holding the object's monitor (inside
synchronized (obj)), otherwiseIllegalMonitorStateException.Always wait in a
whileloop, neverif: threads can wake spuriously, and another thread may have consumed the condition first.Prefer
notifyAll()unless all waiters are interchangeable and only one can proceed.
public class Main {
public static void main(String[] args) {
Object o = new Object();
try {
o.wait(); // not holding o's monitor!
} catch (IllegalMonitorStateException | InterruptedException e) {
System.out.println(e.getClass().getSimpleName());
}
}
}IllegalMonitorStateExceptionA bounded buffer with wait/notifyAll
import java.util.ArrayDeque;
import java.util.Queue;
public class Main {
public static void main(String[] args) throws InterruptedException {
BoundedBuffer buf = new BoundedBuffer(2);
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) buf.put(i);
buf.put(-1); // poison pill: "no more items"
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
Thread consumer = new Thread(() -> {
try {
int sum = 0;
for (int x; (x = buf.take()) != -1; ) {
System.out.println("consumed " + x);
sum += x;
}
System.out.println("sum = " + sum);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
producer.start(); consumer.start();
producer.join(); consumer.join();
}
}
class BoundedBuffer {
private final Queue<Integer> items = new ArrayDeque<>();
private final int capacity;
BoundedBuffer(int capacity) { this.capacity = capacity; }
synchronized void put(int x) throws InterruptedException {
while (items.size() == capacity) wait(); // full: wait for space
items.add(x);
notifyAll(); // wake consumers
}
synchronized int take() throws InterruptedException {
while (items.isEmpty()) wait(); // empty: wait for data
int x = items.remove();
notifyAll(); // wake producers
return x;
}
}consumed 1
consumed 2
consumed 3
consumed 4
consumed 5
sum = 15The modern way: BlockingQueue
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Main {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
Thread producer = new Thread(() -> {
try {
for (String job : new String[]{"resize", "upload", "notify"}) queue.put(job);
queue.put("DONE");
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
producer.start();
for (String job; !(job = queue.take()).equals("DONE"); ) {
System.out.println("processing " + job);
}
producer.join();
System.out.println("queue empty: " + queue.isEmpty());
}
}processing resize
processing upload
processing notify
queue empty: trueBlockingQueue op | Full / empty behaviour |
|---|---|
put(e) / take() | Block until space / an element is available |
offer(e) / poll() | Return false / null immediately |
offer(e, t, unit) / poll(t, unit) | Block up to a timeout |
add(e) / remove() | Throw an exception |
Implementations: ArrayBlockingQueue (bounded, array), LinkedBlockingQueue (optionally bounded), PriorityBlockingQueue, SynchronousQueue (capacity 0 — a direct hand-off), DelayQueue.
Deadlock
Here T1 locks a then wants b, while T2 locks b then wants a. The latch makes the bad interleaving certain; the JVM's ThreadMXBean detects the cycle (the same thing jstack reports).
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
Object a = new Object(), b = new Object();
CountDownLatch bothHoldOne = new CountDownLatch(2);
Thread t1 = worker("T1", a, b, bothHoldOne);
Thread t2 = worker("T2", b, a, bothHoldOne); // opposite order!
t1.start(); t2.start();
while (t1.getState() != Thread.State.BLOCKED || t2.getState() != Thread.State.BLOCKED) {
Thread.sleep(10);
}
ThreadMXBean mx = ManagementFactory.getThreadMXBean();
System.out.println("deadlocked threads: " + mx.findDeadlockedThreads().length);
System.out.println("T1 waits for a lock owned by " + mx.getThreadInfo(t1.threadId()).getLockOwnerName());
System.out.println("T2 waits for a lock owned by " + mx.getThreadInfo(t2.threadId()).getLockOwnerName());
}
static Thread worker(String name, Object first, Object second, CountDownLatch latch) {
Thread t = new Thread(() -> {
synchronized (first) {
latch.countDown();
try { latch.await(); } catch (InterruptedException e) { return; }
synchronized (second) { System.out.println(name + " finished"); }
}
}, name);
t.setDaemon(true); // lets the JVM exit despite the deadlock
return t;
}
}deadlocked threads: 2
T1 waits for a lock owned by T2
T2 waits for a lock owned by T1Avoiding deadlock: a global lock order
public class Main {
public static void main(String[] args) throws InterruptedException {
Account x = new Account(1, 1000), y = new Account(2, 1000);
Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) transfer(x, y, 1); });
Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) transfer(y, x, 2); });
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("x = " + x.balance + ", y = " + y.balance + ", total = " + (x.balance + y.balance));
}
static void transfer(Account from, Account to, int amount) {
Account first = from.id < to.id ? from : to; // always lock the lower id first
Account second = first == from ? to : from;
synchronized (first) {
synchronized (second) {
from.balance -= amount;
to.balance += amount;
}
}
}
}
class Account {
final int id;
int balance;
Account(int id, int balance) { this.id = id; this.balance = balance; }
}x = 2000, y = 0, total = 2000Why must wait() be called inside a while loop?
wait() vs sleep()?
Why are wait/notify defined on Object and not Thread?
How do you detect a deadlock in production?