Given a multi-stage parallel job where workers may join or finish between stages, which coordination primitive would you choose and why?
answer
- Multi-stage => reusable barrier needed
- Dynamic membership => party count must change
- Latch one-shot, Barrier fixed count => both rejected
- Phaser: register / arriveAndDeregister / arriveAndAwaitAdvance
- Downgrade to simpler tool if constraints relax
basics
~10 sUse a Phaser. It is reusable for the multiple stages and lets workers register or deregister between stages, which CountDownLatch (one-shot) and CyclicBarrier (fixed count) cannot do.
solid answer
~50 sI'd pick Phaser. The job has two defining traits: multiple stages (so I need a barrier that resets each round) and a varying set of participants (workers join late or finish early). CountDownLatch is out because it is one-shot - you'd need a fresh latch per stage. CyclicBarrier is reusable but its party count is fixed at construction, so it can't absorb workers joining or leaving between stages. Phaser handles both: arriveAndAwaitAdvance() at each stage boundary acts as the reusable barrier, register()/bulkRegister() adds newcomers, and arriveAndDeregister() lets finished workers drop out without deadlocking the rest. I'd override onAdvance to run end-of-stage logic and to terminate when no parties remain. If the participant count were actually fixed I'd downgrade to the simpler CyclicBarrier; if it were a single gate, CountDownLatch. Exchanger is irrelevant here - it only swaps data between exactly two threads, not a group rendezvous.
go deeper
Recognizes that a reusable barrier is needed and names Phaser as the flexible option.
Eliminates CountDownLatch (one-shot) and CyclicBarrier (fixed count) and maps Phaser's register/deregister to the join/leave requirement.
Explains the deadlock that a fixed barrier suffers, uses arriveAndDeregister and onAdvance correctly, and applies the 'simplest tool that fits' downgrade rule.
Frames the choice as a trade-off of flexibility vs complexity, considers scalability (tiered phasers) and newer structured-concurrency alternatives, and sets a team guideline for when each primitive is warranted.
## Reading the requirements Two phrases in the question decide everything: 1. **"multi-stage"** — there is more than one round; whatever I use must work *again* after each stage. This rules out one-shot devices. 2. **"workers may join or finish between stages"** — the number of participants is **not fixed**. This rules out devices with a hard-coded party count. ## Evaluating each candidate - **`CountDownLatch`** — a latch counts down once to zero and is then spent; it is *not reusable*. To use it for N stages you'd allocate N latches and hand-roll the rotation. It also has no notion of a changing party set. **Reject.** - **`CyclicBarrier`** — reusable (good for the stages) and runs a barrier action at each boundary, but its party count is **fixed at construction**. A worker joining or leaving between stages would break the count: too few arrivals and survivors block forever; too many and you'd have to reconstruct the barrier. **Reject** for the dynamic-membership requirement. - **`Phaser`** — reusable across phases *and* supports dynamic membership. **Accept.** - **`Exchanger`** — only ever pairs *two* threads to swap a value; it is not a group rendezvous at all. **Irrelevant** to a multi-worker barrier. - **`Semaphore`** — limits concurrent access to a resource; it doesn't make parties wait for *each other* at a boundary. **Wrong tool.** ## How Phaser satisfies it - **Stage boundary** — every worker calls `arriveAndAwaitAdvance()` at the end of a stage. The phaser blocks each until all *currently registered* parties have arrived, then advances the phase and releases them together. Reusable for every stage, automatically. - **A worker joins** — call `register()` (or `bulkRegister(k)`) before the next stage; the phaser's width grows and the new worker participates in subsequent rounds. - **A worker finishes** — call `arriveAndDeregister()`; it signals arrival for the current stage *and* removes itself, so it neither blocks the others nor is expected in future stages. Critically, this is *non-blocking*, so the leaving thread doesn't wait. - **End-of-stage logic / termination** — override `onAdvance(phase, parties)` to do per-stage bookkeeping and return `true` to terminate (the default terminates when parties reach zero), so the last worker leaving won't strand anyone. ## The deadlock subtlety With a fixed barrier, a worker that finishes early but stops arriving would leave the barrier permanently one short — survivors deadlock. Phaser's `arriveAndDeregister()` exists precisely to avoid this: the leaver formally reduces the expected count so the remaining workers' barrier still completes. ## The downgrade rule Always pick the *simplest tool that fits*. If the membership were actually fixed, `CyclicBarrier` is simpler and clearer. If there were one stage only, `CountDownLatch` is simpler. Phaser earns its extra complexity *only* because both 'multi-stage' and 'dynamic membership' are present here. (In very recent Java, structured concurrency / `StructuredTaskScope` may express some of these flows more cleanly, but Phaser remains the precise classic answer.)
- Why not just create a new CyclicBarrier with the updated count before each stage?You can, but it is fragile and verbose: you must recompute the count, coordinate every thread to switch to the new barrier atomically, and handle the window where some threads still reference the old one. Phaser does the dynamic counting internally with register/deregister, which is safer and clearer.
- How does deregistering prevent a deadlock that a fixed barrier would suffer?A fixed barrier expects exactly N arrivals; if a worker finishes and stops arriving, only N-1 ever show up and the rest block forever. arriveAndDeregister() reduces the phaser's expected count, so the remaining workers' barrier completes with the smaller party set.
A book club that meets weekly (stages) where members can join or quit between meetings: you need a sign-in sheet whose roster updates each week, not a fixed guest list.
saying these in an interview costs you the question
- Choosing CyclicBarrier without noting its fixed party count breaks under join/leave
- Choosing CountDownLatch and ignoring that it is single-use, not multi-stage
- Suggesting Exchanger or Semaphore, which solve unrelated problems
- Over-engineering with Phaser when the membership is actually fixed (CyclicBarrier would do)