Keyboard Shortcuts N Next post
P Previous post
S Save / unsave
R Read aloud
T Toggle theme
/ Focus search
Esc Close panels
🔥
Ready to read...
Java Java and Cryptography Mastery Series — Extended Java Programming Java Tutorial Module 19 - Advanced Collections and Concurrency

Java Phaser for Advanced Synchronization with Phases

Reviewed & accurate
AI Summary

Introduction

The Java Phaser class joined the JDK in Java 7 (2011) as part of JSR 166y, the second wave of java.util.concurrent additions. It generalises CyclicBarrier and CountDownLatch with two key capabilities: dynamic registration (parties can be added at any time) and an explicit phase counter that increments each time all registered parties arrive. A phaser starts at phase 0 and moves to phase 1, 2, 3, and so on as cycles complete, and the onAdvance callback lets subclasses observe or veto each phase transition.

For a Java learner, the java phaser primitive is the right tool when you need a barrier that grows or shrinks over time, when workers may join or leave the computation between phases, or when you want different behaviour per phase. This post walks through the phaser mechanics, demonstrates a multi-phase worker pattern with dynamic registration, and shows how to override onAdvance to terminate the phaser after a fixed number of phases.

What is Java Phaser?

The Java Phaser class is a synchronisation barrier that supports both a dynamic set of registered parties and an explicit phase number. Construction can take an initial party count; register and bulkRegister(int) add parties later, and arriveAndDeregister removes the calling thread's party. The phase number is exposed via getPhase, and it increments after the last registered party arrives for the current phase.

Conceptually, Phaser sits between CyclicBarrier and CountDownLatch. Like CyclicBarrier, it resets and can be reused, and like CountDownLatch, it can separate awaiters from counters. But Phaser adds three features neither has: dynamic registration, explicit phases with a monotonic counter, and the onAdvance hook that lets a subclass customise per-phase behaviour. Phaser also supports tiering: you can build a tree of phasers where leaves coordinate small groups and the root coordinates the groups, which scales better than one giant barrier.

A Phaser is not a perfect drop-in for either CyclicBarrier or CountDownLatch. CyclicBarrier is simpler when the party count is fixed and known up front; CountDownLatch is simpler when you need a one-shot fan-in. Use Phaser when you need the dynamic and phase-aware features.

How Java Phaser Works - Step by Step

Step 1: Construction. You write new Phaser(3) to start a phaser at phase 0 with three registered parties, or new Phaser for an empty phaser to which parties are added later. The state is an internal 64-bit long that packs the phase, party count, arrival count, and termination flag.

Step 2: arrive. A party calls arrive to record arrival at the current phase without waiting. Internally, the arrival count is incremented. When the arrival count equals the party count, the phase advances: phase increments, arrival count resets, and any waiting threads are woken. arrive returns the phase at which the caller arrived.

Step 3: awaitAdvance(int phase). A thread that wants to wait for the phase to advance calls awaitAdvance with the phase it observed. If the current phase is greater than the observed phase, the call returns immediately; otherwise the thread parks until the phase advances.

Step 4: arriveAndAwaitAdvance combines arrive and await in one call: the caller registers its arrival and parks until the phase advances. This is the most common idiom, and it mirrors CyclicBarrier.await.

Step 5: arriveAndDeregister records arrival and removes the calling party, useful when a worker is finished and should not be counted on later phases. When the party count drops to zero, the phaser is not automatically terminated; onAdvance returns true to terminate it.

Step 6: onAdvance(int phase, int registeredParties) is the hook that runs once per phase advance. The default implementation returns true when phase is greater than or equal to a configured maximum, terminating the phaser. Subclasses override this method to customise termination or to perform side effects per phase.

Java Program for Phaser

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

public class PhaserDemo {

    // A phaser that terminates after MAX_PHASES phases
    static final class BoundedPhaser extends Phaser {
        static final int MAX_PHASES = 3;

        BoundedPhaser(int parties) {
            super(parties);
        }

        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
            System.out.println("onAdvance phase=" + phase
                    + " registered=" + registeredParties);
            return phase + 1 >= MAX_PHASES || registeredParties == 0;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BoundedPhaser phaser = new BoundedPhaser(0);
        ExecutorService pool = Executors.newFixedThreadPool(4);

        for (int w = 0; w < 3; w++) {
            final int id = w;
            phaser.register(); // dynamic registration
            pool.submit(() -> {
                while (!phaser.isTerminated()) {
                    int phase = phaser.getPhase();
                    System.out.println("worker " + id + " phase " + phase + " work");
                    phaser.arriveAndAwaitAdvance();
                    if (id == 2 && phase == 0) {
                        // worker 2 adds a latecomer after phase 0
                        phaser.register();
                        System.out.println("worker 2 registered a new party");
                    }
                }
            });
        }

        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
        System.out.println("phaser demo done, final phase=" + phaser.getPhase());
    }
}

Code Explanation - Line by Line

The BoundedPhaser subclass overrides onAdvance to terminate the phaser after MAX_PHASES phases. The default onAdvance returns true when the registered party count is zero; the override adds a phase-count upper bound so workers exit after a bounded number of cycles. The two boolean conditions are OR-ed so either condition terminates the phaser.

The main method constructs the phaser with zero parties, then registers three parties dynamically by calling phaser.register before submitting each worker. The submit-then-register order is a common idiom when worker counts are not known at construction time. Each worker runs the same loop: read the current phase, do the phase's work, then arriveAndAwaitAdvance.

The output of getPhase inside the loop reads the current phase before arrival. After arriveAndAwaitAdvance returns, the phase has advanced, so the next iteration prints the next phase number. When the loop's body for phase 0 completes in worker 2, that worker registers an additional party; the next phase will therefore wait for four parties instead of three, demonstrating dynamic registration.

The isTerminated check at the top of each worker loop is the exit condition. When onAdvance returns true after the final phase, the phaser moves into the terminated state and isTerminated returns true; each worker's loop then exits. Because onAdvance checks phase + 1 >= MAX_PHASES, the phaser terminates after the third phase.

The pool shutdown at the end gives workers up to five seconds to drain. The final phase number printed is 3, because the phaser has advanced three times before terminating. The onAdvance callback prints its arguments so you can watch the phase counter increment and the registered party count change.

Sample Input and Output

Input:
  start with 3 parties, MAX_PHASES=3, worker 2 registers one extra party after phase 0

Output (interleaving will vary, but the phase sequence is deterministic):
  worker 0 phase 0 work
  worker 1 phase 0 work
  worker 2 phase 0 work
  worker 2 registered a new party
  onAdvance phase=0 registered=4
  worker 0 phase 1 work
  worker 1 phase 1 work
  worker 2 phase 1 work
  onAdvance phase=1 registered=4
  worker 0 phase 2 work
  worker 1 phase 2 work
  worker 2 phase 2 work
  onAdvance phase=2 registered=4
  phaser demo done, final phase=3

The phase counter increments after each onAdvance call; the third onAdvance (phase 2) returns true, terminating the phaser. The final getPhase value of 3 reflects one increment past the last worked phase.

Common Mistakes to Avoid

Mistake 1: Forgetting to register before submit. If you submit a worker that calls arriveAndAwaitAdvance but never called register, that worker's arrival is not counted, and the phaser never advances. Always register the party first.

Mistake 2: Calling arrive without awaitAdvance when you actually need to wait. arrive returns immediately; only awaitAdvance and arriveAndAwaitAdvance wait for the next phase. The two-step form is useful for fire-and-forget tasks but is a footgun when you needed the wait.

Mistake 3: Letting onAdvance return true too early. The default behaviour of onAdvance is to terminate when registeredParties is zero; if you override this method to add a side effect and forget to call super or replicate the zero check, the phaser may never terminate.

Mistake 4: Not handling the latecomer problem. A thread that registers after the current phase has already begun may have to wait a full phase before its first arrival counts. Always register before the phase work starts, or design the worker to handle a no-op first phase.

Real-World Applications

Parallel iterative algorithms. Solver phases where each phase depends on the previous one's result use Phaser as the barrier; the dynamic registration is useful when the active worker set changes between phases, such as in work-stealing schedulers.

Map-reduce style aggregation. Each map task registers with a phaser, finishes its phase, and deregisters; the onAdvance hook can run the reduce step after the final map task arrives. This pattern appears in custom in-memory map-reduce implementations and in fork-join recursive decompositions.

Test fixtures. Test frameworks that need to coordinate setup, run, and teardown phases across threads use Phaser; the per-phase onAdvance hook runs the transition code.

Game loop phases. Frame-based game servers use a phaser between the update and render phases; the phase counter doubles as the frame counter, and the registered party count tracks active entities.

Tiered barriers. For very large worker counts, a tree of phasers scales better than a single barrier; child phasers register with their parent, and the parent advances only when all children have arrived. This is exactly the design used by the JDK's fork-join pool internally for some operations.

Conclusion

Phaser is the most general barrier in java.util.concurrent, with dynamic registration, an explicit phase counter, and a per-phase onAdvance hook. It is the right choice when worker counts vary, when per-phase customisation is needed, or when a tiered barrier structure is required. The next topics worth studying are CyclicBarrier for the simpler fixed-party case, CountDownLatch for one-shot fan-in, and ForkJoinPool for the work-stealing executor that hosts phaser-coordinated tasks.

Test Your Knowledge
How did you find this?

Comments

Join the discussion! Sign in with your Google or Blogger account, or comment as Anonymous - no account needed. For quick questions, also reach me on Telegram @cytestch.

Comments