FeatureCountDownLatchCyclicBarrierPhaser
ReusableNoYes (auto-reset)Yes (multiple phases)
Dynamic partiesNoNoYes — register/deregister at any time
Phase trackingNoImplicitYes — explicit phase number
TerminationManualManualBuilt-in via onAdvance()
Tiered hierarchyNoNoYes — parent Phaser for large groups
ComplexityLowMediumHigh
import java.util.concurrent.Phaser; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class PhaserExample { public static void main(String[] args) { int parties = 3; // Register the main thread + N workers Phaser phaser = new Phaser(1 + parties); // 1 for main thread ExecutorService pool = Executors.newFixedThreadPool(parties); for (int i = 0; i < parties; i++) { final int id = i; pool.submit(() -> { // Phase 1: data loading System.out.println("Worker " + id + " loading data (phase " + phaser.getPhase() + ")"); phaser.arriveAndAwaitAdvance(); // wait for all workers // Phase 2: processing System.out.println("Worker " + id + " processing (phase " + phaser.getPhase() + ")"); phaser.arriveAndAwaitAdvance(); // Phase 3: writing output System.out.println("Worker " + id + " writing output (phase " + phaser.getPhase() + ")"); phaser.arriveAndDeregister(); // deregister when done }); } // Main thread participates in phase 1 and 2, then deregisters phaser.arriveAndAwaitAdvance(); // wait for phase 1 System.out.println("Main: all data loaded"); phaser.arriveAndAwaitAdvance(); // wait for phase 2 System.out.println("Main: all processing done"); phaser.arriveAndDeregister(); // main done pool.shutdown(); } } arriveAndAwaitAdvance() signals arrival and waits for all other parties. arriveAndDeregister() signals arrival and permanently removes the caller from future phases — the party count drops by one.
MethodDescription
register()Dynamically add one party; returns new phase number
bulkRegister(n)Add n parties at once
arrive()Signal arrival without waiting (non-blocking)
arriveAndAwaitAdvance()Signal arrival and block until phase advances
arriveAndDeregister()Signal arrival and remove self from future phases
awaitAdvance(phase)Wait for the given phase to complete (non-participating)
awaitAdvanceInterruptibly(phase)Same but responds to interruption
getPhase()Current phase number (starts at 0; negative when terminated)
getRegisteredParties()Number of registered parties
getArrivedParties()Number that have arrived for the current phase
forceTermination()Terminate the phaser immediately

Parties can register at any time (even from within a running phase). This enables fork-join style patterns where tasks spawn sub-tasks that join the same phaser.

import java.util.concurrent.Phaser; Phaser phaser = new Phaser(1); // start with just main thread // Dynamically register and start workers for (int i = 0; i < 5; i++) { phaser.register(); // register before starting the thread final int id = i; new Thread(() -> { System.out.println("Worker " + id + " in phase " + phaser.getPhase()); phaser.arriveAndDeregister(); // arrive and deregister when done }).start(); } // Main thread waits for all workers at phase 0 phaser.arriveAndAwaitAdvance(); System.out.println("Phase 0 complete; parties left: " + phaser.getRegisteredParties());

Override onAdvance(int phase, int registeredParties) to run code after each barrier trip and to decide when the Phaser should terminate. Returning true terminates it.

import java.util.concurrent.Phaser; int maxPhases = 3; Phaser phaser = new Phaser(3) { @Override protected boolean onAdvance(int phase, int registeredParties) { System.out.printf("Phase %d complete — %d parties remaining%n", phase, registeredParties); // Terminate after maxPhases phases or when all parties deregistered return phase >= maxPhases - 1 || registeredParties == 0; } }; // Workers run until phaser terminates Runnable task = () -> { while (!phaser.isTerminated()) { doPhaseWork(phaser.getPhase()); phaser.arriveAndAwaitAdvance(); } System.out.println("Worker done — phaser terminated"); }; new Thread(task).start(); new Thread(task).start(); new Thread(task).start(); When a Phaser terminates, getPhase() returns a negative number. arriveAndAwaitAdvance() returns immediately on a terminated Phaser without throwing, making it safe to check isTerminated() in a loop.

A single Phaser with many parties has internal contention on its state word. For very large party counts, build a tree of Phasers — child Phasers propagate to a parent automatically.

import java.util.concurrent.Phaser; // Root phaser — no registered parties itself; children auto-register Phaser root = new Phaser(); // Each child phaser registers one party on the root // and manages its own group of 100 workers Phaser child1 = new Phaser(root, 100); Phaser child2 = new Phaser(root, 100); // Workers use child phasers Runnable workerGroup1 = () -> { child1.arriveAndAwaitAdvance(); // synchronise within group 1 }; // Root advances only after both children complete their phases // This scales to millions of parties with O(log n) contention
import java.util.concurrent.Phaser; Phaser phaser = new Phaser(3); // A monitoring thread observes phase transitions without participating Thread monitor = new Thread(() -> { int phase = 0; while (!phaser.isTerminated()) { // Block until phase 'phase' completes phase = phaser.awaitAdvance(phase) + 1; System.out.println("Monitor: phase advanced to " + phaser.getPhase()); } }); monitor.setDaemon(true); monitor.start();