Many coordination mistakes happen because developers choose a tool by API familiarity instead of by synchronization shape.

The most important early distinction in this module is:

  • one-shot gates
  • reusable barriers

They sound related because both make threads wait. But they solve different timing problems.


Problem Statement

Suppose you are coordinating worker threads in one of these two situations.

Situation A:

  • the main thread must wait until three warm-up jobs finish
  • after that, the service starts serving traffic
  • the event happens once

Situation B:

  • four workers process data in rounds
  • after each round, all workers must wait for the others
  • then they all begin the next round together

These are not the same problem.

If you use a latch for B, you get a one-time release but no repeated rendezvous. If you use a barrier for A, you add complexity for a flow that never repeats.


One-Shot Gate Mental Model

A one-shot gate says:

  • wait until some fixed condition has been satisfied once
  • then let waiting threads proceed
  • never close the gate again

That is exactly the model of CountDownLatch.

Typical examples:

  • startup readiness
  • waiting for a fixed set of tasks
  • tests waiting for N async signals

The important word is one-shot.


Reusable Barrier Mental Model

A reusable barrier says:

  • a fixed group of threads must meet at a rendezvous point
  • no one proceeds to the next step until everyone arrives
  • after release, the same pattern can happen again

That is the model of CyclicBarrier.

Phaser generalizes the idea further by allowing:

  • multiple phases
  • dynamic party registration and deregistration

So reusable barriers are about repeated synchronization points, not just one-time readiness.


Runnable Comparison Example

The following class shows both shapes side by side.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class GateVsBarrierDemo {

    public static void main(String[] args) throws Exception {
        runStartupGateExample();
        runReusableBarrierExample();
    }

    static void runStartupGateExample() throws Exception {
        CountDownLatch startupLatch = new CountDownLatch(3);
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (String taskName : new String[]{"cache", "routes", "thresholds"}) {
            executor.submit(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                    System.out.println("Startup task complete: " + taskName);
                } finally {
                    startupLatch.countDown();
                }
            });
        }

        startupLatch.await();
        System.out.println("Startup gate opened once");
        executor.shutdown();
    }

    static void runReusableBarrierExample() throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(3,
                () -> System.out.println("Round completed, starting next round"));
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int workerId = 1; workerId <= 3; workerId++) {
            final int id = workerId;
            executor.submit(() -> {
                for (int round = 1; round <= 2; round++) {
                    System.out.println("Worker " + id + " finished round " + round);
                    barrier.await();
                }
                return null;
            });
        }

        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.SECONDS);
    }
}

The contrast is the point:

  • the latch opens once and stays open
  • the barrier synchronizes the same group repeatedly

Where Phaser Enters the Picture

If CountDownLatch is the one-shot gate and CyclicBarrier is the reusable barrier, Phaser is the more flexible phase controller.

Choose Phaser when you need:

  • repeated phases
  • parties joining later
  • parties leaving early
  • one coordinator that wants explicit phase numbers

That flexibility is powerful, but it also means a higher cognitive cost than the simpler primitives.


Typical Misclassifications

Using CountDownLatch for repeating rounds

This usually leads to awkward code such as:

  • creating a new latch every iteration
  • coordinating latch replacement manually
  • introducing subtle race windows around round transitions

If the workers must meet every round, start with CyclicBarrier or Phaser.

Using CyclicBarrier for one-time startup

You can make it work, but it communicates the wrong thing.

A startup gate is usually not a repeated rendezvous. CountDownLatch explains the intent more directly.

Ignoring party lifecycle

The moment workers may:

  • join late
  • leave early
  • skip future rounds

simple barrier logic becomes fragile. That is the moment to consider Phaser.


Decision Heuristics

Use CountDownLatch when:

  • the event count is fixed
  • the wait happens once
  • the gate should remain open after release

Use CyclicBarrier when:

  • a fixed group of threads must repeatedly meet
  • all parties are symmetric
  • the barrier must reset automatically each round

Use Phaser when:

  • coordination happens in multiple named phases
  • party membership can change
  • you need richer phase-aware control

The easiest way to choose is to ask:

  • am I waiting for a batch to finish once, or
  • am I coordinating the same group across rounds

That question often decides the primitive immediately.


Testing and Debugging Notes

For one-shot gates, debugging usually focuses on:

  • which event never decremented the latch
  • whether the count was wrong

For reusable barriers, debugging usually focuses on:

  • which thread never arrived this round
  • whether one thread timed out and broke the barrier

That difference is another sign that these primitives solve structurally different problems.


Key Takeaways

  • One-shot gates and reusable barriers are different coordination shapes, not just different APIs.
  • CountDownLatch models one-time readiness or fixed batch completion.
  • CyclicBarrier models repeated rendezvous for a fixed set of parties.
  • Phaser extends barrier-style coordination when phases and party membership are more dynamic.

Next post: CyclicBarrier in Java Deep Dive

Comments