Definition
- Dynamic registration – parties can join with
register()and leave witharriveAndDeregister()at any time during any phase. CountDownLatch and CyclicBarrier require fixed party counts at construction. - Multi-phase – automatically advances through numbered phases (0, 1, 2, ...). Each call to
arriveAndAwaitAdvance()blocks until all registered parties arrive, then all advance to the next phase together. - onAdvance callback –
onAdvance(int phase, int registeredParties)runs after all parties arrive and before the next phase begins. Return true to terminate the Phaser. - Hierarchical tiering – child Phasers can register with a parent Phaser to reduce CAS contention in large-scale synchronization (hundreds of threads).
- Termination – the Phaser terminates when
onAdvance()returns true, when all parties deregister, or whenforceTermination()is called.
CountDownLatch is a one-time gate at National Bank of India: wait for exactly 5 tellers to arrive, then open the gate. You cannot change the count to 6. CyclicBarrier is a checkpoint gate: exactly 5 auditors synchronize at each checkpoint, but you cannot add a 6th mid-audit. Phaser is an adaptive meeting: auditors can join and leave between meetings. Meeting 1: 5 auditors. Meeting 2: 3 left, 2 new ones joined, so 4 auditors. Meeting 3: 1 more leaves, 3 synchronize. No fixed count required.
Understanding the Concepts
What is a Phaser?
Phaser is the most flexible synchronization barrier in java.util.concurrent. It supports dynamic party registration, multiple phases, and a callback between phases. It can replace both CountDownLatch (one-time wait) and CyclicBarrier (fixed-count reusable barrier) while adding capabilities neither provides.
package com.bank.concurrency;
import java.util.concurrent.Phaser;
public class PhaserDemo {
public static void main(String[] args) {
Phaser phaser = new Phaser(3); // 3 initial parties
for (int i = 0; i < 3; i++) {
final int workerId = i;
new Thread(() -> {
System.out.println("Worker-" + workerId + ": phase 0 work");
phaser.arriveAndAwaitAdvance(); // sync at phase 0→1
System.out.println("Worker-" + workerId + ": phase 1 work");
phaser.arriveAndAwaitAdvance(); // sync at phase 1→2
System.out.println("Worker-" + workerId + ": done");
phaser.arriveAndDeregister(); // leave
}, "Worker-" + i).start();
}
}
}
How does dynamic registration work?
package com.bank.concurrency;
import java.util.concurrent.Phaser;
public class DynamicRegistrationDemo {
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser(1); // main thread is party 1
// Dynamically add workers
for (int i = 0; i < 3; i++) {
phaser.register(); // add a party
final int id = i;
new Thread(() -> {
System.out.println("Worker-" + id + " processing");
phaser.arriveAndDeregister(); // done, leave
}, "Worker-" + id).start();
}
phaser.arriveAndAwaitAdvance(); // main waits for all workers
System.out.println("All workers complete");
}
}
How does onAdvance work?
package com.bank.concurrency;
import java.util.concurrent.Phaser;
public class OnAdvanceDemo {
public static void main(String[] args) {
Phaser phaser = new Phaser(3) {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("Phase " + phase + " complete. Parties: "
+ registeredParties);
return phase >= 2 || registeredParties == 0;
// Terminate after phase 2 or when all parties leave
}
};
// ... use phaser in worker threads
}
}
How is Phaser different from CountDownLatch and CyclicBarrier?
| Feature | CountDownLatch | CyclicBarrier | Phaser |
|---|---|---|---|
| Fixed parties | Yes (at construction) | Yes (at construction) | No (dynamic) |
| Reusable | No (one-time) | Yes (auto-reset) | Yes (auto-advance) |
| Phases | 1 (count to 0) | Multiple (trips) | Multiple (numbered) |
| Join/leave mid-run | No | No | Yes (register/deregister) |
| Callback | No | Yes (barrier action) | Yes (onAdvance) |
| Termination | count reaches 0 | reset() | onAdvance returns true |
What is a real-world banking use case?
An ETL pipeline at National Bank of India processes data in three phases: extract, transform, and load. The number of workers varies between phases because some workers finish early or new workers are added for the heavier phases.
package com.bank.etl;
import java.util.concurrent.Phaser;
public class ETLPipelineDemo {
public static void main(String[] args) {
Phaser phaser = new Phaser(1); // coordinator
// Phase 0: Extract (10 workers)
for (int i = 0; i < 10; i++) startWorker(phaser, "Extract-" + i);
phaser.arriveAndAwaitAdvance(); // wait for phase 0
// Phase 1: Transform (5 workers, others deregistered)
for (int i = 0; i < 5; i++) startWorker(phaser, "Transform-" + i);
phaser.arriveAndAwaitAdvance(); // wait for phase 1
// Phase 2: Load (3 workers)
for (int i = 0; i < 3; i++) startWorker(phaser, "Load-" + i);
phaser.arriveAndAwaitAdvance(); // wait for phase 2
phaser.arriveAndDeregister();
System.out.println("ETL pipeline complete");
}
static void startWorker(Phaser phaser, String name) {
phaser.register();
new Thread(() -> {
System.out.println(name + " processing");
phaser.arriveAndDeregister();
}, name).start();
}
}
What are common interview follow-up questions?
- Can a Phaser deadlock? Yes, if a registered party never arrives. Use
awaitAdvanceInterruptibly()with a timeout to prevent permanent blocking. - What is hierarchical tiering? Large Phasers (hundreds of parties) suffer CAS contention on the internal state. Tiering groups parties under child Phasers that register with a parent, distributing the synchronization overhead.
Visual Overview
Phaser: Dynamic Parties Across Phases ═════════════════════════════════════ Phase 0 (Extract): W1 W2 W3 W4 W5 (5 parties) ──── arriveAndAwaitAdvance() ──── Phase 1 (Transform): W1 W2 W3 (W4, W5 deregistered) ──── arriveAndAwaitAdvance() ──── Phase 2 (Load): W1 W6 (W2, W3 deregistered, W6 registered) ──── arriveAndAwaitAdvance() ──── Terminated (onAdvance returns true)
Key Takeaway
Phaser is the most flexible synchronization barrier in java.util.concurrent, combining CountDownLatch's one-time wait with CyclicBarrier's reusable checkpoints and adding dynamic party registration that neither provides. Parties can join with register() and leave with arriveAndDeregister() at any phase, making it ideal for multi-phase algorithms where the number of participants changes between stages.
The onAdvance callback runs between phases and can terminate the Phaser when processing is complete. For large-scale synchronization (hundreds of threads), hierarchical tiering distributes CAS contention across child Phasers registered with a parent.
At National Bank of India, an ETL pipeline used Phaser to coordinate variable worker counts across three phases: 10 extract workers, 5 transform workers, and 3 load workers. CyclicBarrier could not handle the shrinking party count, but Phaser handled it naturally with arriveAndDeregister().
Remember one sentence: Phaser is the dynamic-party, multi-phase synchronization barrier that lets threads join and leave at any phase, replacing both CountDownLatch and CyclicBarrier.
---