☕ Java

Phaser

Phaser is the most flexible synchronization barrier in java.util.concurrent, introduced in Java 7 as a generalization of both CountDownLatch and CyclicBarrier. Unlike those two, Phaser supports dynamic registration and deregistration of parties at any time, an unlimited number of reusable phases, separation of arrival from waiting (a thread can arrive at a barrier without blocking), hierarchical composition of phasers for tree-structured parallelism, and a customizable termination condition via the overridable onAdvance() method. Phaser's internal state encodes the current phase number and the registered party counts in a single long, enabling efficient lock-free state transitions via CAS. Threads advance phases by calling arrive() (arrive without waiting), arriveAndAwaitAdvance() (arrive and wait for all others), or arriveAndDeregister() (arrive and permanently deregister). awaitAdvance(int phase) waits for the phaser to advance past a given phase. This entry covers all Phaser API methods, phase numbering and termination, arrival vs waiting semantics, tree-structured phasers for scalable fork-join, the onAdvance customization hook, and migration patterns from CountDownLatch and CyclicBarrier.

Phaser API — Registration, Arrival, and Phase Advancement

A Phaser is constructed with new Phaser() (zero initial parties) or new Phaser(int parties) (pre-registered parties). Additional parties are registered at any time with register() (adds one party) or bulkRegister(int parties) (adds multiple). A party is deregistered with arriveAndDeregister() or with a call to register() paired with arriveAndDeregister(). When the registered party count reaches zero, the Phaser terminates — subsequent calls to arrive methods return a negative phase number indicating termination. The arrival methods advance a party through the current phase. arrive() increments the arrival count for the current phase and returns immediately without waiting — the thread does not block. This is useful for threads that contribute to the barrier count but have other work to do while waiting for others. arriveAndAwaitAdvance() increments the arrival count and blocks until all registered parties have arrived, then returns the new phase number. arriveAndDeregister() increments the arrival count, deregisters the calling party (reducing the party count for future phases), and returns without waiting — the thread will never participate in subsequent phases. awaitAdvance(int phase) blocks until the Phaser advances past the specified phase number, then returns the new phase. If the Phaser is already past that phase, it returns immediately. awaitAdvanceInterruptibly(int phase) throws InterruptedException if interrupted while waiting. awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit) adds a timeout. The Phaser maintains a phase counter starting at zero. When all registered parties arrive, the phase advances (incremented modulo Integer.MAX_VALUE), onAdvance() is called, and all waiting threads are released. The phase number can be read at any time with getPhase(); a negative return value indicates the Phaser has terminated. getRegisteredParties(), getArrivedParties(), and getUnarrivedParties() provide real-time state for monitoring.
Java
// ── Basic Phaser usage ───────────────────────────────────────────────
Phaser phaser = new Phaser(1);   // register main thread as party (count = 1)

int WORKERS = 4;
for (int i = 0; i < WORKERS; i++) {
    phaser.register();            // register each worker dynamically (count grows to 5)
    final int id = i;
    new Thread(() -> {
        System.out.println("Worker " + id + " phase 0: working");
        phaser.arriveAndAwaitAdvance();  // arrive + wait for all 5 parties

        System.out.println("Worker " + id + " phase 1: working");
        phaser.arriveAndDeregister();    // arrive + deregister — no more phases for this worker
    }).start();
}

// Main thread participates in phase 0, then deregisters:
phaser.arriveAndAwaitAdvance();   // main thread arrives at phase 0
System.out.println("Phase 0 complete — main thread releasing");
phaser.arriveAndDeregister();     // main deregisters (count back to 4 workers only)

// ── arrive() without waiting ──────────────────────────────────────────
Phaser asyncPhaser = new Phaser(3);   // 3 parties

new Thread(() -> {
    doExpensiveWork();
    int phase = asyncPhaser.arrive();  // arrive without waiting — returns current phase
    System.out.println("Worker arrived at phase " + phase + ", continuing other work");
    doOtherWork();   // this thread does not wait for the barrier
}).start();

// Another thread can wait for a specific phase to complete:
int next = asyncPhaser.awaitAdvance(0);   // wait for phase 0 to complete
System.out.println("Phase 0 complete, new phase: " + next);

// ── Phase number and termination ─────────────────────────────────────
Phaser p = new Phaser(2);
System.out.println("Phase: " + p.getPhase());   // 0
p.arrive(); p.arrive();    // both parties arrive → phase advances
System.out.println("Phase: " + p.getPhase());   // 1

// Phaser terminates when registered parties = 0:
Phaser terminating = new Phaser(1);
int result = terminating.arriveAndDeregister();  // last party deregisters → terminates
System.out.println("Phase after termination: " + terminating.getPhase());  // negative

// ── Introspection ─────────────────────────────────────────────────────
Phaser info = new Phaser(5);
info.arrive(); info.arrive();   // 2 parties arrive
System.out.println("Registered:  " + info.getRegisteredParties());  // 5
System.out.println("Arrived:     " + info.getArrivedParties());     // 2
System.out.println("Unarrived:   " + info.getUnarrivedParties());   // 3
System.out.println("Phase:       " + info.getPhase());              // 0 (not advanced yet)
System.out.println("Terminated:  " + info.isTerminated());          // false

onAdvance, Hierarchical Phasers, and Dynamic Party Management

The onAdvance(int phase, int registeredParties) method is called by the Phaser each time all parties arrive, just before waiting threads are released. Subclasses override it to implement custom termination logic or inter-phase computation. The method returns true to terminate the Phaser (no more phases) or false to continue to the next phase. The default implementation terminates when registeredParties == 0 — which is the behavior that makes arriveAndDeregister() cause termination when the last party deregisters. Overriding onAdvance() is the Phaser equivalent of the barrier action in CyclicBarrier, but with access to the phase number and party count and with termination control. Hierarchical phasers scale to very large thread counts. A flat Phaser with 10,000 registered parties is a bottleneck: every arrival updates a shared CAS-contested state variable. A tree of Phasers reduces this to O(log N) contention: leaf phasers each serve a small number of threads, and each leaf Phaser's arrival triggers arrival at its parent Phaser, which in turn may trigger arrival at a grandparent. A leaf Phaser is registered as a sub-party of its parent by passing the parent Phaser to the child Phaser's constructor: new Phaser(parentPhaser, initialParties). When a child Phaser advances (all its parties arrive), it automatically arrives at the parent Phaser. This tree structure is transparent to the individual threads — they only interact with their leaf Phaser. Dynamic party management enables work-stealing and fork-join patterns. A thread that forks additional sub-tasks can call phaser.register() before spawning each sub-task (adding a party) and pass the Phaser to the sub-task. When the sub-task completes, it calls arriveAndDeregister(). The parent thread calls arriveAndAwaitAdvance() to wait for all sub-tasks (including dynamically forked ones) to complete. This pattern handles variable-sized parallel workloads where the number of tasks is not known at construction time.
Java
// ── Custom onAdvance: limited phases ─────────────────────────────────
public class LimitedPhaser extends Phaser {
    private final int maxPhases;

    public LimitedPhaser(int parties, int maxPhases) {
        super(parties);
        this.maxPhases = maxPhases;
    }

    @Override
    protected boolean onAdvance(int phase, int registeredParties) {
        System.out.println("Phase " + phase + " complete — "
            + registeredParties + " parties remaining");
        boolean terminate = (phase >= maxPhases - 1) || (registeredParties == 0);
        if (terminate) System.out.println("Phaser terminating after phase " + phase);
        return terminate;   // true = terminate after this phase
    }
}

LimitedPhaser limited = new LimitedPhaser(3, 2);  // 3 parties, 2 phases max

for (int i = 0; i < 3; i++) {
    final int id = i;
    new Thread(() -> {
        for (int phase = 0; !limited.isTerminated(); phase++) {
            System.out.println("Worker " + id + " executing phase " + phase);
            int next = limited.arriveAndAwaitAdvance();
            if (next < 0) break;   // negative = terminated
        }
    }).start();
}

// ── Hierarchical Phaser: tree structure for 1000 threads ─────────────
public static Phaser buildHierarchicalPhaser(int totalThreads, int fanout) {
    Phaser root = new Phaser();
    buildPhaser(root, totalThreads, fanout);
    return root;
}

private static void buildPhaser(Phaser parent, int parties, int fanout) {
    if (parties <= fanout) {
        // Leaf: register parties directly with parent
        parent.bulkRegister(parties);
    } else {
        // Internal node: create child phasers
        for (int i = 0; i < parties; i += fanout) {
            int batch = Math.min(fanout, parties - i);
            // Child phaser registered as a sub-party of parent:
            Phaser child = new Phaser(parent, 0);
            buildPhaser(child, batch, fanout);
        }
    }
}

// Each leaf thread uses its leaf Phaser:
// new Phaser(parentPhaser, 0) registers the child with the parent
// When all children of a phaser advance, the child Phaser triggers parent arrival

// ── Dynamic fork: register before forking, deregister when done ───────
public static void dynamicFork(Phaser phaser, int depth) {
    phaser.register();   // register this task as a party
    System.out.println("Task registered, depth=" + depth);

    if (depth < 3) {
        // Fork two sub-tasks:
        Thread t1 = new Thread(() -> dynamicFork(phaser, depth + 1));
        Thread t2 = new Thread(() -> dynamicFork(phaser, depth + 1));
        t1.start(); t2.start();
        try { t1.join(); t2.join(); }
        catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }

    // Work done: deregister this task
    phaser.arriveAndDeregister();
    System.out.println("Task deregistered, depth=" + depth);
}

Phaser dynamicPhaser = new Phaser();  // no initial parties
dynamicFork(dynamicPhaser, 0);        // starts dynamic tree
// Phaser terminates when all dynamically registered parties deregister

Migrating from CountDownLatch and CyclicBarrier, and Comparison

Every CountDownLatch can be expressed as a Phaser, and the migration is mechanical. A CountDownLatch(N) waiting for N tasks to complete becomes a Phaser(N+1): the N worker threads call arriveAndDeregister(), and the waiting thread calls arriveAndAwaitAdvance() followed by arriveAndDeregister(). The +1 is for the waiting thread to participate in the phase. A one-shot starting gun (CountDownLatch(1), N threads await(), one thread countDown()) becomes a Phaser(1): N threads call awaitAdvance(0), the signaling thread calls arriveAndDeregister(), which advances the Phaser (since it was the only registered party), unblocking all awaitAdvance(0) callers. Every CyclicBarrier(N) becomes a Phaser(N) where all threads call arriveAndAwaitAdvance() instead of await(). The barrier action maps to overriding onAdvance(). Broken barrier state maps to Phaser termination. The CyclicBarrier's automatic reset maps to the Phaser's automatic phase advancement. The Phaser equivalent has additional capabilities CyclicBarrier does not: threads can deregister between phases (variable party count per phase), and the termination condition can be customized. The performance profile: Phaser uses more complex state encoding (a 64-bit long encoding phase, registered count, and arrived count) than CountDownLatch or CyclicBarrier, which adds slightly more per-operation overhead. For very simple cases, CountDownLatch and CyclicBarrier are marginally lighter. For any case requiring dynamic registration, multiple phases with variable participants, or hierarchical tree barriers, Phaser is the correct tool and its generality is worth the overhead. A practical migration guide: if the code creates a new CountDownLatch or CyclicBarrier for each iteration of a loop, it can almost certainly be simplified with a Phaser that persists across iterations with automatic reset. If the party count changes between iterations, Phaser is necessary. If the termination condition is "after N phases" rather than "when parties reaches zero," override onAdvance().
Java
// ── CountDownLatch → Phaser migration ────────────────────────────────
// BEFORE: CountDownLatch
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        doWork();
        latch.countDown();
    }).start();
}
latch.await();

// AFTER: Phaser (equivalent)
Phaser phaser = new Phaser(4);  // 3 workers + 1 main thread
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        doWork();
        phaser.arriveAndDeregister();  // arrive and deregister (like countDown)
    }).start();
}
phaser.arriveAndAwaitAdvance();   // main thread arrives and waits (like await)
phaser.arriveAndDeregister();     // main deregisters — Phaser terminates

// ── CyclicBarrier → Phaser migration ─────────────────────────────────
// BEFORE: CyclicBarrier with barrier action
CyclicBarrier cb = new CyclicBarrier(3, () -> aggregateResults());
for (int phase = 0; phase < 5; phase++) {
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            computePhase();
            try { cb.await(); } catch (Exception e) {}
        }).start();
    }
}

// AFTER: Phaser with onAdvance
Phaser migrated = new Phaser(3) {
    @Override protected boolean onAdvance(int phase, int parties) {
        aggregateResults();   // equivalent to barrier action
        return phase >= 4;    // terminate after 5 phases (0-4)
    }
};
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        while (!migrated.isTerminated()) {
            computePhase();
            migrated.arriveAndAwaitAdvance();
        }
    }).start();
}

// ── Full comparison matrix ────────────────────────────────────────────
//
//                    CountDownLatch  CyclicBarrier   Phaser
// ──────────────────────────────────────────────────────────────────────
// Reusable?           No              Yes             Yes
// Dynamic parties?    No              No              Yes
// Symmetric?          No              Yes             Configurable
// Arrive w/o wait?    No              No              Yes (arrive())
// Custom termination? No              No              Yes (onAdvance)
// Hierarchical?       No              No              Yes (parent Phaser)
// Broken state?       No              Yes             N/A (termination)
// Phase counter?      No              No              Yes
// Best use            One-time events Iterative alg.  All complex cases
//                     Starting guns   Fixed parties   Dynamic/variable

// ── awaitAdvance for phase-specific waiting ───────────────────────────
Phaser coordinator = new Phaser(1);

// Background thread advances through phases:
new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        try { Thread.sleep(200); } catch (InterruptedException e) {}
        System.out.println("Advancing to phase " + (i + 1));
        coordinator.arrive();   // advance the phaser
    }
    coordinator.arriveAndDeregister();
}).start();

// Wait only for phase 3 to complete:
int completedPhase = coordinator.awaitAdvance(3);
System.out.println("Phase 3 done, now at phase: " + completedPhase);  // 4

Related Topics in Multithreading

Process vs Thread
A process is an independent program in execution with its own isolated memory space, file handles, and system resources, managed by the operating system and separated from all other processes by strict boundaries. A thread is a unit of execution that lives inside a process, sharing that process's memory, heap, and resources with every other thread in the same process. Java programs run inside a JVM process; the JVM itself creates and manages threads, and every Java application starts with at least one thread — the main thread — with additional threads created by the JVM for garbage collection, JIT compilation, signal handling, and other runtime tasks. Understanding the distinction between processes and threads is the foundation for all concurrent programming in Java: it determines what is shared and what is isolated, what is fast and what is expensive, what fails independently and what fails together. This entry covers the OS-level and JVM-level model of processes and threads, the memory model that follows from the shared-versus-isolated distinction, the cost model for creation and context switching, failure isolation and its consequences, inter-process and inter-thread communication mechanisms, and the practical decision of when to use multiple processes versus multiple threads.
Thread Basics
A Java thread is an instance of java.lang.Thread that represents an independent path of execution within the JVM process. Every thread has a lifecycle — from creation through runnable, running, blocked, waiting, timed-waiting, and terminated states — and a set of properties including its name, priority, daemon status, thread group, and uncaught exception handler. The Java memory model specifies what visibility guarantees exist between threads and when writes by one thread are guaranteed to be visible to another. Thread scheduling is controlled by the OS scheduler subject to hints from the JVM via thread priority; the JVM does not provide real-time scheduling guarantees. This entry covers the complete thread lifecycle and its state machine, thread properties and how they affect scheduling and JVM shutdown, the happens-before relationship and why it matters for visibility, daemon threads and their relationship to JVM shutdown, thread interruption as a cooperative cancellation mechanism, and the methods on Thread that every Java developer must understand.
Creating Threads
Java provides three primary abstractions for defining the work a thread will execute: the Thread class itself (subclassed to override run()), the Runnable interface (a task with no return value and no checked exception), and the Callable interface (a task with a return value and a declared checked exception). Each represents a different contract between the task and the infrastructure that runs it. Thread subclassing couples the task definition to the execution mechanism and is the oldest and least flexible approach. Runnable decouples the task from the thread, allowing the same Runnable to be submitted to thread pools, scheduled executors, or wrapped in Thread objects. Callable extends that decoupling to include a return value and exception propagation, returning a Future that allows the caller to retrieve the result or handle exceptions asynchronously. Understanding all three — their contracts, their limitations, and when to use each — is the foundation of concurrent programming in Java before reaching for higher-level constructs.
Thread Lifecycle
The Java thread lifecycle is the complete sequence of states a thread passes through from the moment a Thread object is constructed to the moment its execution ends. Java defines six states in the Thread.State enum — NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, and TERMINATED — and the JVM transitions threads between these states in response to specific method calls, lock acquisitions, monitor notifications, timeouts, and exceptions. Each state has a precise meaning, a defined set of entry conditions, and a defined set of exit conditions. Understanding the lifecycle in full is prerequisite knowledge for diagnosing deadlocks, thread leaks, performance bottlenecks in thread dumps, and incorrect synchronization — all of which manifest as threads stuck in specific states. This entry covers every state in the lifecycle with its entry and exit conditions, all legal and illegal state transitions, how thread dumps represent each state, the interaction between lifecycle states and interruption, the effect of uncaught exceptions on lifecycle, and how to observe lifecycle transitions programmatically.