Concurrency
Print In Order — CountDownLatch vs wait/notify
Three threads must print 'first', 'second', 'third' in order, regardless of scheduling. Compare CountDownLatch (clean one-shot signaling) against synchronized wait/notify (general state machine).
Problem#
One object, three methods, three threads — thread A calls first(), thread B calls second(), thread C calls third(). You have no control over which thread the OS runs first, but the output must always be:
firstsecondthird
Even if thread C runs before thread A, third() must wait until both first() and second() have completed.
Think Before Coding#
Work through these before looking at the solutions:
-
What primitive lets one thread signal "done" and another wait for it? Several options: CountDownLatch, Semaphore, synchronized with wait/notify, or even a volatile flag. Which is cleanest for a one-time handoff?
-
Why not a plain boolean flag without volatile or synchronization? Without a memory barrier, the JVM can keep the write in a CPU cache or reorder it — the thread waiting on the flag may never see the update and spin forever (or proceed on a stale value). volatile forces the write to be visible across threads; synchronized provides the same visibility guarantee plus atomicity.
-
CountDownLatch(1) vs Semaphore(0) for one-shot signaling? Both work. A semaphore with 0 initial permits blocks acquire() until release() is called — identical to a latch. CountDownLatch is more readable for "wait until N events happen" because its API says exactly that.
Solution 1: CountDownLatch — cleanest for one-shot handoffs#
package PrintInOrder;
import java.util.concurrent.CountDownLatch;
public class PrintInOrder {
// Each latch represents one one-time event: "this step is done"
private final CountDownLatch firstDone = new CountDownLatch(1);
private final CountDownLatch secondDone = new CountDownLatch(1);
public void first() {
System.out.print("first");
firstDone.countDown(); // signal: first() is done
}
public void second() throws InterruptedException {
firstDone.await(); // block until first() completes
System.out.print("second");
secondDone.countDown(); // signal: second() is done
}
public void third() throws InterruptedException {
secondDone.await(); // block until second() completes
System.out.print("third");
}
}
Why this is the right tool here: Each handoff is a one-time, one-directional signal — "step N is done, proceed." That's exactly what CountDownLatch models. Once counted down to 0, every current and future await() on that latch returns immediately (it can't "un-fire"), so there's no risk of a thread that calls await() after countDown() blocking forever.
Solution 2: synchronized with wait/notifyAll — general state machine#
package PrintInOrder;
public class PrintInOrder1 {
private int state = 1; // 1 = wait for first, 2 = wait for second, 3 = wait for third
private final Object lock = new Object();
public void first() throws InterruptedException {
synchronized (lock) {
System.out.print("first");
state = 2;
lock.notifyAll(); // wake all waiters so they re-check state
}
}
public void second() throws InterruptedException {
synchronized (lock) {
while (state != 2) // loop: re-verify after every wakeup
lock.wait();
System.out.print("second");
state = 3;
lock.notifyAll();
}
}
public void third() throws InterruptedException {
synchronized (lock) {
while (state != 3)
lock.wait();
System.out.print("third");
state = 4;
lock.notifyAll();
}
}
}
Why while around wait()? notifyAll() wakes every thread on the lock, including threads waiting for different state values. A thread woken this way must re-check its own condition — it may not be its turn yet. Also, wait() can return spuriously for OS-level reasons unrelated to any signal.
Why notifyAll() not notify()? Three threads can all be parked on the same lock waiting for different state values. notify() wakes exactly one — the wrong one could be woken, leaving the right one sleeping indefinitely.
Demo#
package PrintInOrder;
public class Demo {
public static void main(String[] args) throws InterruptedException {
PrintInOrder1 p = new PrintInOrder1();
Thread t1 = new Thread(() -> {
try { p.first(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
Thread t2 = new Thread(() -> {
try { p.second(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
Thread t3 = new Thread(() -> {
try { p.third(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
t1.start();
t3.start(); // third starts before second — order still enforced
t2.start();
t1.join(); t2.join(); t3.join();
System.out.println("\nAll threads finished.");
}
}
Expected output#
firstsecondthird
All threads finished.
thread3 starts before thread2 in the demo — the ordering is enforced by the latches/state machine, not by start order.
Which approach to prefer?#
| CountDownLatch | synchronized + wait/notifyAll | |
|---|---|---|
| State managed | None (latch fires once) | int state + lock |
| Spurious wakeup | Not possible | while loop required |
| Missed signal risk | None (late await() returns immediately) | None (state re-checked on every wakeup) |
| Code verbosity | Minimal | More boilerplate |
| Reusable | No (CountDownLatch is one-shot) | Yes (state can be reset) |
| Best for | Fixed sequence of one-time signals | Arbitrary condition waiting, repeated |
For this problem: CountDownLatch is the better fit. The wait/notify version is valuable to understand because CountDownLatch, Semaphore, and all higher-level utilities are built on the same wait/notify/monitor foundation — knowing the primitive makes the abstractions transparent.
Common Mistakes#
| Mistake | Why it breaks |
|---|---|
| if instead of while around wait() | Spurious wakeup causes a thread to proceed when state hasn't advanced |
| notify() instead of notifyAll() | Wakes only one thread; if it's not the one whose turn it is, the correct thread sleeps forever |
| Plain boolean without volatile or synchronized | Visibility not guaranteed across threads — JVM can cache the value in a register |
| CountDownLatch(0) | Latch is already done; await() returns immediately for everyone — no ordering enforced |
| Calling countDown() before printing | The next thread wakes and prints before this one finishes — wrong order |