Skip to Content
Course content

259: The Phaser for Multi-Phase Synchronization

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably already used CountDownLatch or CyclicBarrier, so you know how to make threads wait for each other. But in the real world, synchronization isn't always a one-and-done deal. Sometimes you have a process that moves through distinct stages—where everyone must finish Stage A before anyone can start Stage B—and the number of participants might change along the way. That's where the Phaser comes in.

Wait, why do I need this if I already know CyclicBarrier?

The biggest limitation of a CyclicBarrier is that the number of parties is fixed when you create it. If you decide halfway through your process that you need three more worker threads to help out, or if one thread crashes and needs to be removed, CyclicBarrier can't handle that gracefully.

I like to think of Phaser as a more flexible, dynamic version of a barrier. It allows you to register new parties on the fly and let others deregister themselves without breaking the synchronization logic. Plus, it's built specifically for "phased" work, meaning it keeps an internal count of which phase you're currently in (Phase 0, Phase 1, etc.), which makes debugging complex pipelines much easier.

How do I actually implement a multi-step workflow with it?

Let's imagine we're building a simple data processing pipeline: first we load raw data, then we transform it, and finally we save it. Every single worker thread must finish loading before any thread starts transforming.

Phaser phaser = new Phaser(1); // Register the main thread

for (int i = 0; i < 3; i++) {
    phaser.register(); // Register each worker
    new Thread(() -> {
        // Phase 0: Loading
        System.out.println(Thread.currentThread().getName() + " loading data...");
        phaser.arriveAndAwaitAdvance(); 

        // Phase 1: Transforming
        System.out.println(Thread.currentThread().getName() + " transforming data...");
        phaser.arriveAndAwaitAdvance();

        // Phase 2: Saving
        System.out.println(Thread.currentThread().getName() + " saving data...");
        phaser.arriveAndDeregister();
    }).start();
}

// The main thread also needs to arrive and advance to let workers move forward
phaser.arriveAndDeregister(); 

The magic happens with arriveAndAwaitAdvance(). This tells the phaser, "I'm done with my part of the current phase, and I'll sit tight until everyone else is done too." Once the last registered party arrives, the phaser increments the phase number and wakes everyone up to start the next block of code.

What happens if some threads finish their work early and need to drop out?

This is where Phaser really shines. In a complex system, not every worker is needed for every stage. Maybe only one thread needs to handle the "Save" phase, while ten threads were needed for the "Loading" phase.

Instead of calling arriveAndAwaitAdvance(), a thread can call arriveAndDeregister(). This tells the phaser, "I'm done with my work for the rest of the entire process; don't wait for me in future phases."

If you don't deregister threads that are finished, your program will hang forever because the phaser will keep waiting for a thread that's already terminated. I've seen this happen in production more times than I'd like to admit. Always make sure your exit paths call arriveAndDeregister().

Can I execute a specific action between phases?

Yes, and you don't have to do it manually in the main thread. You can override the onAdvance method. This method is triggered automatically whenever the phaser advances to the next phase.

Phaser phaser = new Phaser(3) {
    @Override
    protected boolean onAdvance(int phase, int registeredParties) {
        System.out.println("Phase " + phase + " completed. Parties: " + registeredParties);
        // Return true to terminate the phaser, false to keep it going
        return registeredParties == 0; 
    }
};

I find this incredibly useful for logging progress or performing a "cleanup" task (like merging partial results from workers) before the next phase begins.




📋 Practical Task

Exercise: Implementing a Three-Stage Game Asset Loader

You are building a game engine that loads assets in three distinct phases: Loading Textures, Initializing Shaders, and Finalizing Scene Graph.

Write a program using a Phaser that satisfies the following requirements:

  • Create 3 worker threads to handle the loading.
  • All 3 workers must complete "Loading Textures" before any move to "Initializing Shaders".
  • After the shader phase, one of the workers is no longer needed and should deregister itself.
  • The remaining 2 workers must complete "Finalizing Scene Graph" before the program exits.
  • Override the onAdvance method to print exactly which phase just finished (e.g., "Phase 0 complete", "Phase 1 complete").

Ensure that your main thread coordinates the startup and waits for the phaser to terminate or for all workers to finish before printing "Game Ready!".

Rating
0 0

There are no comments for now.

to be the first to leave a comment.