Phaser is the most flexible coordination utility in this part of the JDK.

It is what you reach for when CountDownLatch is too one-shot and CyclicBarrier is too rigid.

Its strengths are:

  • repeated phases
  • explicit phase numbers
  • parties that can register and deregister dynamically

That flexibility makes it powerful, and also easier to misunderstand at first.


Problem Statement

Suppose a multi-stage workflow has several rounds:

  1. load input
  2. validate
  3. transform
  4. publish

Now add two complications:

  • different workers may join late
  • some workers may leave after finishing their part

That is where the simpler coordination tools start to strain.

CountDownLatch is one-shot. CyclicBarrier assumes a stable fixed party count.

Phaser exists for the more dynamic phase-oriented case.


Mental Model

A Phaser tracks:

  • the current phase number
  • the number of registered parties
  • how many of those parties have arrived in the current phase

For each phase:

  1. parties register
  2. each party arrives
  3. when all registered parties have arrived, the phase advances
  4. the next phase begins
  5. parties may deregister when they are done

This is like a barrier with a richer lifecycle model.


Core API

Important operations include:

  • register(): add one new participating party
  • bulkRegister(n): add several parties
  • arrive(): signal arrival without waiting
  • arriveAndAwaitAdvance(): signal arrival and wait for others
  • arriveAndDeregister(): signal completion and leave future phases
  • getPhase(): inspect the current phase number
  • getRegisteredParties(): inspect the party count

The existence of both arrival and registration APIs is the key difference from simpler barriers.


Runnable Example

This example models a three-worker import pipeline with three coordinated phases.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

public class PhaserDemo {

    public static void main(String[] args) throws Exception {
        Phaser phaser = new Phaser(1); // main thread registers first
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int workerId = 1; workerId <= 3; workerId++) {
            final int id = workerId;
            phaser.register();
            executor.submit(() -> runWorker(id, phaser));
        }

        for (int phase = 0; phase < 3; phase++) {
            System.out.println("Main waiting for phase " + phase);
            phaser.arriveAndAwaitAdvance();
        }

        phaser.arriveAndDeregister();
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
    }

    static void runWorker(int workerId, Phaser phaser) {
        try {
            doPhase(workerId, phaser, "load input");
            doPhase(workerId, phaser, "validate records");
            doPhase(workerId, phaser, "transform rows");
        } finally {
            phaser.arriveAndDeregister();
        }
    }

    static void doPhase(int workerId, Phaser phaser, String work) {
        try {
            System.out.println("Worker " + workerId + " starting: " + work);
            TimeUnit.MILLISECONDS.sleep(100L * workerId);
            System.out.println("Worker " + workerId + " finished: " + work);
            phaser.arriveAndAwaitAdvance();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }
}

Notice what this example demonstrates:

  • one coordinator thread can participate too
  • each phase has a distinct number
  • parties can deregister when their work is done

That is exactly the kind of flexibility CountDownLatch and CyclicBarrier do not provide as directly.


Where Phaser Fits Well

Strong use cases:

  • staged batch pipelines
  • simulations with multiple rounds and changing participants
  • tests coordinating several asynchronous actors over phases
  • hierarchical or nested coordination where parties do not all live forever

The best sign that Phaser fits is when you naturally describe the workflow in terms of phases and party membership, not just in terms of “wait until N.”


When It Is Too Much

Phaser is easy to overuse.

Poor fits:

  • simple one-time startup waiting
  • fixed recurring barriers with a stable party count
  • workflows better expressed as futures or queues

If the simpler tool already expresses the design clearly, prefer it. Phaser earns its complexity only when you really need its lifecycle features.


Common Mistakes

Registering the wrong number of parties

If a thread participates without registering, or registers and never arrives, the phase logic breaks.

Forgetting to deregister

If a party leaves the workflow but stays registered, future phases may block forever waiting for a participant that is gone.

Mixing arrival styles carelessly

Using arrive(), arriveAndAwaitAdvance(), and arriveAndDeregister() in the same workflow is fine, but only when the semantics are deliberate.

Otherwise the phase math becomes hard to reason about.


Testing and Debugging Notes

When diagnosing phaser problems, inspect:

  • current phase number
  • registered party count
  • which actor failed to arrive or deregister

Practical logging helps:

  • log before each arrival
  • log phase numbers explicitly
  • log deregistration points

Compared with simpler primitives, Phaser bugs often come from lifecycle mismatches rather than from the API itself.


Decision Guide

Choose Phaser when:

  • coordination happens in named or repeated phases
  • participants may join or leave
  • you need something more flexible than a fixed barrier

Do not choose it when:

  • a single latch is enough
  • a fixed recurring barrier is enough
  • the coordination is really better modeled as task completion or future composition

Key Takeaways

  • Phaser is the flexible phase-coordination utility for repeated rounds with dynamic party membership.
  • It supports registration, arrival, waiting, and deregistration explicitly.
  • It is stronger than CountDownLatch and CyclicBarrier when workflow phases and party lifecycle both matter.
  • Use it only when that extra flexibility is genuinely part of the problem.

Next post: Semaphore in Java Deep Dive

Comments