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).

August 10, 2026

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:

  1. 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?

  2. 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.

  3. 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#

java
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#

java
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#

java
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?#

CountDownLatchsynchronized + wait/notifyAll
State managedNone (latch fires once)int state + lock
Spurious wakeupNot possiblewhile loop required
Missed signal riskNone (late await() returns immediately)None (state re-checked on every wakeup)
Code verbosityMinimalMore boilerplate
ReusableNo (CountDownLatch is one-shot)Yes (state can be reset)
Best forFixed sequence of one-time signalsArbitrary 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#

MistakeWhy 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 synchronizedVisibility 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 printingThe next thread wakes and prints before this one finishes — wrong order