skip to content

Phaser & Exchanger

Phaser is a reusable multi-phase barrier that lets parties register and deregister dynamically, and Exchanger is a two-thread swap point. Less common in practice, so interviewers use them to gauge how deep your java.util.concurrent knowledge goes.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

Given a multi-stage parallel job where workers may join or finish between stages, which coordination primitive would you choose and why?

level: seniorimportance: should knowfreq 26%

answer

  1. Multi-stage => reusable barrier needed
  2. Dynamic membership => party count must change
  3. Latch one-shot, Barrier fixed count => both rejected
  4. Phaser: register / arriveAndDeregister / arriveAndAwaitAdvance
  5. Downgrade to simpler tool if constraints relax

basics

~10 s

Use 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 s

I'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

for a junior

Recognizes that a reusable barrier is needed and names Phaser as the flexible option.

for a middle

Eliminates CountDownLatch (one-shot) and CyclicBarrier (fixed count) and maps Phaser's register/deregister to the join/leave requirement.

for a senior

Explains the deadlock that a fixed barrier suffers, uses arriveAndDeregister and onAdvance correctly, and applies the 'simplest tool that fits' downgrade rule.

for a principal

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)

context

open as a page

What is a Phaser and how does it improve on CyclicBarrier and CountDownLatch?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A Phaser is a reusable barrier where threads wait for each other at the end of each phase. Unlike CountDownLatch and CyclicBarrier, the number of parties can grow or shrink at runtime, and it supports many phases.

open as a page

What is java.util.concurrent.Exchanger and when would you use it?

level: middleimportance: nice to knowfreq 18%

basics

~10 s

Exchanger is a meeting point where exactly two threads swap objects. Each thread calls exchange(myValue); both block until the partner arrives, then each receives what the other passed in.

open as a page

Explain how a Phaser's phase number works and how termination changes the behavior of its await methods.

level: principalimportance: nice to knowfreq 14%

basics

~10 s

Each completed round increments the phase number (an int starting at 0). When the phaser terminates, await methods stop blocking and immediately return a negative number, so finishing threads can't deadlock the rest.

open as a page