☕ Java

CyclicBarrier

CyclicBarrier is a synchronization aid that allows a fixed number of threads to wait for each other at a common barrier point, then all proceed simultaneously. Unlike CountDownLatch, which is one-shot, CyclicBarrier automatically resets after each barrier trip, making it suitable for algorithms that execute in repeated phases — each phase ends when all parties arrive at the barrier, the optional barrier action runs, and all threads proceed to the next phase. CyclicBarrier is symmetric: every thread both contributes to the barrier count (by calling await()) and waits for all others. This distinguishes it from CountDownLatch, where the thread counting down need not be the one waiting. The barrier action — an optional Runnable passed at construction — runs exactly once per barrier trip, in the context of the last-arriving thread, before any waiting threads are released. If any thread is interrupted or times out while waiting, all waiting threads receive BrokenBarrierException, and the barrier enters a broken state. This entry covers the full CyclicBarrier API, the broken barrier state and recovery, the barrier action and its threading semantics, iterative parallel algorithm patterns, and comparison with CountDownLatch and Phaser.

CyclicBarrier API — Construction, await, and Barrier Action

CyclicBarrier is constructed with new CyclicBarrier(int parties) or new CyclicBarrier(int parties, Runnable barrierAction). The parties argument specifies the number of threads that must call await() before any are released. The barrierAction, if provided, runs once per barrier trip in the context of the last thread to arrive at the barrier, just before all waiting threads are released. await() is the only coordination method. Each thread calls it when it reaches the barrier point; the call blocks until parties threads have called await(). The last thread to call await() runs the barrier action (if any), then all threads are simultaneously released, and the barrier resets automatically. await() returns the arrival index of the calling thread — 0 for the last to arrive, parties-1 for the first — which allows the barrier action to identify the thread that ran it, though the barrierAction Runnable has no parameters and must use closures for this. await(long timeout, TimeUnit unit) waits up to the specified duration. If the timeout expires before all parties arrive, the calling thread throws TimeoutException and the barrier is broken. All other threads in the waiting state receive BrokenBarrierException. Once broken, the barrier stays broken until reset() is called. The getNumberWaiting() method returns the number of threads currently waiting at the barrier — useful for monitoring. getParties() returns the fixed party count. isBroken() returns whether the barrier is in the broken state. reset() manually resets the barrier to its initial state. If threads are currently waiting when reset() is called, they receive BrokenBarrierException. reset() is the mechanism for error recovery: after detecting or handling the cause of a break, reset() allows the barrier to be reused for a new set of tasks. The cyclic name comes from this reset capability — the barrier cycles through trip after trip.
Java
// ── Basic CyclicBarrier: 3 threads synchronized at a meeting point ─────
CyclicBarrier barrier = new CyclicBarrier(3, () ->
    System.out.println("=== Barrier reached — all 3 threads proceed ==="));

for (int i = 0; i < 3; i++) {
    final int id = i;
    new Thread(() -> {
        try {
            System.out.println("Thread " + id + ": working");
            Thread.sleep((long)(Math.random() * 300));
            System.out.println("Thread " + id + ": arrived at barrier");
            int index = barrier.await();   // blocks until all 3 arrive
            // index = 0 for last to arrive, 2 for first
            System.out.println("Thread " + id + ": released (arrival index=" + index + ")");
        } catch (BrokenBarrierException | InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
}
// Thread 0: working
// Thread 1: working
// Thread 2: working
// Thread 1: arrived at barrier        ← order varies
// Thread 0: arrived at barrier
// Thread 2: arrived at barrier
// === Barrier reached — all 3 threads proceed ===   ← barrier action runs
// Thread 2: released (arrival index=0)   ← last to arrive
// Thread 0: released (arrival index=2)
// Thread 1: released (arrival index=1)

// ── Automatic reset: barrier is reused across phases ─────────────────
CyclicBarrier phaseBarrier = new CyclicBarrier(3);
for (int phase = 1; phase <= 3; phase++) {
    final int p = phase;
    Thread[] workers = new Thread[3];
    for (int i = 0; i < 3; i++) {
        final int id = i;
        workers[i] = new Thread(() -> {
            try {
                System.out.printf("Phase %d, Thread %d: working%n", p, id);
                Thread.sleep(50);
                phaseBarrier.await();   // barrier automatically resets after each phase
                System.out.printf("Phase %d, Thread %d: done%n", p, id);
            } catch (BrokenBarrierException | InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
    for (Thread w : workers) w.start();
    for (Thread w : workers) w.join();
    System.out.println("--- Phase " + p + " complete ---");
}

// ── getNumberWaiting and isBroken ────────────────────────────────────
CyclicBarrier inspected = new CyclicBarrier(5);
System.out.println("Parties: "   + inspected.getParties());        // 5
System.out.println("Waiting: "   + inspected.getNumberWaiting());  // 0
System.out.println("Broken: "    + inspected.isBroken());          // false

Broken Barrier State and Error Recovery

The broken state is CyclicBarrier's mechanism for propagating failure. If any thread waiting at the barrier is interrupted or times out, the barrier transitions to broken, and all other waiting threads are unblocked with BrokenBarrierException. Any subsequent call to await() on a broken barrier also throws BrokenBarrierException immediately without waiting. The broken state persists until reset() is called. The rationale: in an iterative parallel algorithm, if one thread fails in phase N, the other threads cannot meaningfully proceed to phase N+1 — they depend on the failed thread's output. BrokenBarrierException propagates the failure notification to all participants, allowing each to take corrective action (retry, abort, log) rather than waiting forever for a thread that will never arrive. The recovery pattern: catch BrokenBarrierException in each worker thread, check the cause of the break, and decide whether to retry or abort. If the barrier itself is the shared resource that should be retried, one thread should call reset() after all workers have exited the broken barrier. If each worker should independently decide to retry, a new CyclicBarrier instance is cleaner — creating a new instance avoids any shared mutable state reset race. The barrier action throws an exception: if the Runnable passed as the barrier action throws an unchecked exception, the exception propagates in the last-arriving thread, and all other waiting threads receive BrokenBarrierException. This means exceptions in the barrier action are treated as barrier failures, which is the correct behavior — if the barrier action fails, it is unsafe to proceed to the next phase.
Java
// ── Broken barrier: timeout causes BrokenBarrierException for all ────
CyclicBarrier breakable = new CyclicBarrier(3);
AtomicInteger brokenCount = new AtomicInteger(0);

// Thread 1: times out waiting
new Thread(() -> {
    try {
        breakable.await(100, TimeUnit.MILLISECONDS);  // times out
    } catch (TimeoutException e) {
        System.out.println("Thread 1: timed out — barrier broken");
    } catch (BrokenBarrierException | InterruptedException e) {
        System.out.println("Thread 1: broken barrier");
    }
    brokenCount.incrementAndGet();
}).start();

// Thread 2: receives BrokenBarrierException due to thread 1's timeout
new Thread(() -> {
    try {
        Thread.sleep(200);   // arrives after thread 1 times out
        breakable.await();   // BrokenBarrierException — barrier is already broken
    } catch (BrokenBarrierException e) {
        System.out.println("Thread 2: barrier was broken — aborting");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    brokenCount.incrementAndGet();
}).start();

Thread.sleep(500);
System.out.println("isBroken: " + breakable.isBroken());    // true
System.out.println("Affected: " + brokenCount.get());        // 2

// ── Recovery: reset() after handling the break ────────────────────────
System.out.println("Resetting barrier");
breakable.reset();
System.out.println("isBroken after reset: " + breakable.isBroken());  // false
// Barrier can now be used again for a new set of 3 threads

// ── Barrier action exception breaks the barrier ───────────────────────
CyclicBarrier fragileBarrier = new CyclicBarrier(2, () -> {
    throw new RuntimeException("Barrier action failed!");
});

CountDownLatch observed = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
    final int id = i;
    new Thread(() -> {
        try {
            fragileBarrier.await();
        } catch (BrokenBarrierException e) {
            System.out.println("Thread " + id + ": got BrokenBarrierException");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (RuntimeException e) {
            // Last-arriving thread gets the RuntimeException from the barrier action:
            System.out.println("Thread " + id + ": got RuntimeException from action: " + e.getMessage());
        } finally {
            observed.countDown();
        }
    }).start();
}
observed.await();
// Thread 1: got RuntimeException from action: Barrier action failed!
// Thread 0: got BrokenBarrierException

// ── Resilient iterative algorithm with break detection ─────────────────
public static void resilientPhaseLoop(int parties, int phases, Runnable[] work)
        throws InterruptedException {

    CyclicBarrier barrier = new CyclicBarrier(parties, () ->
        System.out.println("Phase complete"));

    CountDownLatch allDone = new CountDownLatch(parties);
    for (int i = 0; i < parties; i++) {
        final int id = i;
        new Thread(() -> {
            try {
                for (int phase = 0; phase < phases; phase++) {
                    work[id].run();
                    barrier.await();   // synchronize between phases
                }
            } catch (BrokenBarrierException e) {
                System.out.println("Worker " + id + ": barrier broken, aborting");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                allDone.countDown();
            }
        }).start();
    }
    allDone.await();
}

Iterative Parallel Algorithms and Comparison with CountDownLatch

CyclicBarrier is purpose-built for iterative parallel algorithms — algorithms that divide work among N threads, synchronize between steps, and repeat for multiple iterations. The barrier ensures that no thread begins iteration K+1 until all threads have completed iteration K, maintaining the invariant that each iteration's outputs are complete before the next begins. Parallel sorting algorithms (parallel merge sort, bitonic sort), iterative numerical methods (Jacobi iteration for solving linear systems, relaxation methods in physics simulation), multi-pass image processing (each pass reading from the previous pass's output), and game engine update loops (physics, AI, and rendering phases synchronized each frame) all follow this pattern. The barrier action is the hook for inter-phase aggregation: a result that is computed across all threads in phase K and needed by all threads in phase K+1 should be computed in the barrier action. Because the barrier action runs before any thread proceeds to the next phase, and because it holds a happens-before relationship to the return of await() in all waiting threads, the barrier action's writes are guaranteed visible to all threads in the next phase. The comparison with CountDownLatch is straightforward: CountDownLatch is one-shot and asymmetric (N countdown callers, M await callers, not necessarily equal); CyclicBarrier is cyclic and symmetric (exactly N parties, all calling await()). Use CountDownLatch for "wait for N tasks to complete, then proceed once." Use CyclicBarrier for "N threads work in lockstep, phase after phase." A CyclicBarrier(N) with a single phase is functionally equivalent to a CountDownLatch(N) with one awaiting thread (the main thread) and N countdown callers (the workers), but expressed differently — and CyclicBarrier's value is the automatic reset for subsequent phases.
Java
// ── Parallel prefix sum (scan) using CyclicBarrier ───────────────────
// Classic iterative parallel algorithm: O(log n) phases, each with n/2 additions
public static int[] parallelPrefixSum(int[] input) throws InterruptedException {
    int n        = input.length;
    int[] data   = Arrays.copyOf(input, n);
    int phases   = (int)(Math.log(n) / Math.log(2));

    // Barrier action validates intermediate result (optional but illustrative):
    CyclicBarrier barrier = new CyclicBarrier(n / 2, () ->
        System.out.println("Phase complete, data: " + Arrays.toString(data)));

    CountDownLatch done = new CountDownLatch(n / 2);

    for (int t = 0; t < n / 2; t++) {
        final int tid = t;
        new Thread(() -> {
            try {
                for (int phase = 0; phase < phases; phase++) {
                    int stride = 1 << phase;     // 1, 2, 4, 8, ...
                    int idx    = (tid + 1) * 2 * stride - 1;
                    if (idx < n) {
                        data[idx] += data[idx - stride];
                    }
                    barrier.await();             // all threads sync before next phase
                }
            } catch (BrokenBarrierException | InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                done.countDown();
            }
        }).start();
    }
    done.await();
    return data;
}

int[] result = parallelPrefixSum(new int[]{1, 2, 3, 4, 5, 6, 7, 8});
System.out.println(Arrays.toString(result));  // [1, 3, 6, 10, 15, 21, 28, 36]

// ── Game engine update loop ────────────────────────────────────────────
public class GameLoop {
    private static final int THREADS = 4;
    private final CyclicBarrier physicsBarrier  = new CyclicBarrier(THREADS, this::aggregatePhysics);
    private final CyclicBarrier renderBarrier   = new CyclicBarrier(THREADS, this::prepareRender);
    private volatile boolean running = true;

    public void start() {
        for (int i = 0; i < THREADS; i++) {
            final int id = i;
            new Thread(() -> gameThread(id)).start();
        }
    }

    private void gameThread(int id) {
        while (running) {
            try {
                updatePhysics(id);           // each thread updates its entities
                physicsBarrier.await();      // wait for all physics to complete

                updateAI(id);               // AI reads physics results — safe to read
                renderBarrier.await();       // wait for all AI to complete

                submitDrawCalls(id);         // submit draw calls from completed AI state
            } catch (BrokenBarrierException | InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    private void aggregatePhysics()  { /* merge broadphase results */ }
    private void prepareRender()     { /* swap buffers */ }
    private void updatePhysics(int id) { /* simulate assigned entities */ }
    private void updateAI(int id)     { /* update AI for assigned entities */ }
    private void submitDrawCalls(int id){ /* submit render commands */ }
}

// ── CyclicBarrier vs CountDownLatch summary ───────────────────────────
//
//                CyclicBarrier          CountDownLatch
// Reusable?      Yes (auto-reset)        No (one-shot)
// Symmetric?     Yes (all call await)    No (countDown ≠ await callers)
// Party count    Fixed at construction   Fixed at construction
// Barrier action Yes (runs between)      No
// Broken state   Yes (BrokenBarrier)     No (interruption propagates)
// Best for       Iterative phases        Task completion, starting guns

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.