Explanation
What just happened
The latch started at 3. main called await() and parked. Each loader called countDown() when it finished: 3, 2, 1, 0. At 0 the latch opened and main went on to “start serving”.
Try the what-if switch
a loader throws: config’s load() throws before it reaches countDown(). The count sticks at 1, and main waits forever. The stack trace goes to config’s thread, so main never hears about it.
When to use it
When one thread must wait for a fixed number of things to happen:
- start a server only after the DB, cache and config are loaded
- in tests, wait until N worker threads have reached a certain point
The things counting down don’t have to be threads. Any code can call countDown().
Watch out
- Always count down in
finally. If a loader throws beforecountDown(), the count never reaches 0 andawait()waits forever.
try { load(name); } finally { ready.countDown(); }
- Or put a limit on the wait:
ready.await(30, TimeUnit.SECONDS)returnsfalseif time ran out. - A latch is single-use. Once it hits 0 it stays open. For a reusable “everyone meet here” point, use
CyclicBarrierorPhaser.
Common questions
How does CountDownLatch work?
It starts at a count N. await() blocks until the count reaches 0, and each countDown() lowers it by one. Any code can count down, not only threads.
Why does await() hang forever?
A worker probably threw before reaching countDown(), so the count never hit 0. Count down in a finally block, or use await(timeout, unit).
Can a CountDownLatch be reset?
No. It's single-use: once it reaches 0 it stays open. For a reusable meeting point, use CyclicBarrier or Phaser.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us