Mastering Java’s Phaser: Advanced Thread Synchronization for Performance Testing

This article explains Java’s java.util.concurrent.Phaser class, compares it with CountDownLatch, details its core methods, demonstrates practical usage with code examples, and shows how to build a custom lightweight synchronization utility for performance testing scenarios.

FunTester
FunTester
FunTester
Mastering Java’s Phaser: Advanced Thread Synchronization for Performance Testing

Overview of Phaser

Java’s java.util.concurrent.Phaser is a powerful thread‑synchronization utility that supports multi‑phase coordination, dynamic registration and deregistration, and flexible waiting strategies. Compared with CountDownLatch, Phaser can handle an unknown number of participants and allows threads to advance through multiple synchronization phases without blocking.

Core Constructors and Methods

The primary constructor creates a Phaser with an initial number of parties:

public Phaser(int parties) {
    this(null, parties);
}

Key instance methods include:

register() : Increments the number of registered parties and returns the current phase.

public int register() {
    return doRegister(1);
}

arriveAndDeregister() : Signals arrival at the barrier and reduces the party count.

public int arriveAndDeregister() {
    return doArrive(ONE_DEREGISTER);
}

arriveAndAwaitAdvance() : Waits until all parties have arrived before advancing to the next phase (implementation omitted for brevity).

arrive() : Signals arrival without waiting.

public int arrive() {
    return doArrive(ONE_ARRIVAL);
}

awaitAdvance(int phase) : Blocks until the specified phase is reached.

public int awaitAdvance(int phase) {
    final Phaser root = this.root;
    long s = (root == this) ? state : reconcileState();
    int p = (int)(s >>> PHASE_SHIFT);
    if (phase < 0)
        return phase;
    if (p == phase)
        return root.internalAwaitAdvance(phase, null);
    return p;
}

getArrivedParties() : Returns the number of parties that have arrived at the current barrier.

public int getArrivedParties() {
    return arrivedOf(reconcileState());
}

Best Practices for Performance Testing

When using Phaser in performance tests, keep the synchronization logic simple. Avoid multi‑phase coordination unless necessary, as it can increase code complexity and risk deadlocks. Prefer basic registration, arrival, and waiting patterns to maintain clarity and reliability.

Practical Example

The following example demonstrates creating a Phaser, launching three worker threads, and coordinating their completion:

package org.funtester.performance.books.chapter02.section5;

import java.util.concurrent.Phaser;

/**
 * Phaser demonstration class
 */
public class PhaserDemo {
    public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser(1); // main thread registers itself
        for (int i = 0; i < 3; i++) {
            phaser.register(); // each worker registers
            new Thread(() -> {
                phaser.arrive(); // signal task completion
                System.out.println(System.currentTimeMillis() + "  completed " + Thread.currentThread().getName());
            }).start();
        }
        System.out.println(System.currentTimeMillis() + "  total completed: " + phaser.getArrivedParties());
        Thread.sleep(10);
        System.out.println(System.currentTimeMillis() + "  total completed: " + phaser.getArrivedParties());
        phaser.arriveAndAwaitAdvance(); // wait for all workers
        System.out.println(System.currentTimeMillis() + "  total completed after await: " + phaser.getArrivedParties());
    }
}

Output shows that after the initial sleep only two threads have arrived; after the await all three have synchronized, and the final count drops to zero because the phase has advanced.

Typical Use Cases

Phaser shines in scenarios where a large number of tasks must be synchronized across multiple stages, such as bulk user‑profile updates followed by order‑placement performance tests, or pipelines that require registration, execution, and cleanup phases.

Custom Lightweight Synchronizer – FunPhaser

Because Phaser is not designed specifically for performance testing, a simplified custom synchronizer can be built using atomic counters. The FunPhaser class provides registration, completion tracking, and waiting with optional timeouts.

package org.funtester.performance.books.chapter02.section6;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Custom multi‑thread synchronization class
 */
public class FunPhaser {
    AtomicInteger index;   // pending registrations
    AtomicInteger taskNum; // completed tasks

    public FunPhaser() {
        this.index = new AtomicInteger();
        this.taskNum = new AtomicInteger();
    }

    /** Register a task and return the current registration count */
    public int register() {
        return this.index.incrementAndGet();
    }

    /** Mark a task as done */
    public void done() {
        this.index.getAndDecrement();
        this.taskNum.getAndIncrement();
    }

    /** Wait for all tasks to complete (default 100 seconds timeout) */
    public void await() throws InterruptedException {
        long start = System.currentTimeMillis();
        while (index.get() > 0) {
            if (System.currentTimeMillis() - start > 100_000) {
                System.out.println(System.currentTimeMillis() - start);
                break;
            }
            Thread.sleep(100);
        }
    }

    /** Wait with a user‑specified timeout (milliseconds) */
    public void await(int timeout) throws InterruptedException {
        long start = System.currentTimeMillis();
        while (index.get() > 0) {
            if (System.currentTimeMillis() - start >= timeout) {
                break;
            }
            Thread.sleep(100);
        }
    }

    public int queryRegisterNum() {
        return index.get();
    }

    public int queryTaskNum() {
        return taskNum.get();
    }
}

Testing FunPhaser

The test spawns ten threads, each registering ten tasks, simulating work with a short sleep, and then marking each task as done. After all threads finish, the program waits for completion and prints registration and task counts.

FunPhaser phaser = new FunPhaser();
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        for (int j = 0; j < 10; j++) {
            phaser.register();
            try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); }
            finally { phaser.done(); }
        }
    }).start();
}
try {
    phaser.await();
} catch (InterruptedException e) { throw new RuntimeException(e); }
System.out.println("Registered: " + phaser.queryRegisterNum() + " tasks");
System.out.println("Completed: " + phaser.queryTaskNum() + " tasks");

Console output confirms that all 100 tasks are completed while the registration count drops to zero, demonstrating that the custom synchronizer meets the intended performance‑testing requirements.

Conclusion

Phaser offers a versatile, multi‑phase synchronization mechanism suitable for complex concurrent workloads, but its richness can be overkill for simple performance tests. In such cases, a lightweight custom solution like FunPhaser provides the necessary functionality with lower overhead and clearer semantics.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyPerformanceTestingPhaserThreadSynchronization
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.