Knowledge Guide
HomeConcurrencyConcurrency Foundations

5. Barriers

A barrier is a counter guarded by a lock: every thread that calls await() decrements the count and parks on a condition variable; the thread that drives the count to zero runs an optional barrier action, then bumps a generation token and wakes everyone — so all parties threads leave together and the barrier is instantly armed for the next round.

That last clause is the whole point of a CyclicBarrier and the reason it is not a CountDownLatch: a latch counts down to zero once and stays open forever, while a barrier resets itself after each trip so the same object coordinates iterative, phased work (simulation ticks, parallel map/merge rounds, multi-stage pipelines).

Tracing the count-down and the trip

Take new CyclicBarrier(3, mergeAction) — three worker threads must finish a phase before any starts the next. The barrier keeps two pieces of state: count (how many still need to arrive, reset to parties each generation) and a generation object that is the identity of the current round. Here is the exact interleaving when W1 arrives first, then W3, then W2:

StepThreadActioncount afterState
1W1await() → acquire lock, --count (3→2), count≠0 so trip.await() parks W1, lock released2W1 blocked
2W3await()--count (2→1), parks W31W1, W3 blocked
3W2await()--count (1→0): W2 is the tripper0about to trip
4W2runs mergeAction.run() on W2's stack, while still holding the lock0merge runs once
5W2nextGeneration(): trip.signalAll(), install a fresh generation, reset count = parties (0→3)3barrier re-armed
6W1, W3wake, re-check generation, return their arrival index (2 and 1)3all three released

Two details engineers miss: the barrier action runs on whichever thread happened to arrive last (not a dedicated thread), and it runs before anyone is released — so it is the safe place to merge per-thread partial results. await() returns the thread's arrival index (parties-1 for the first in, 0 for the tripper), which you can use to elect one thread for round-level work.

diagram
diagram

Java: a reusable barrier across two phases

This version actually exercises the cyclic property — three workers run two rounds, and the barrier action merges per-round partial sums on the tripper thread. Crucially it joins the workers so main does not exit before output prints (the original example's bug).

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

class BarrierDemo {
  static final int PARTIES = 3, ROUNDS = 2;
  static final AtomicInteger roundSum = new AtomicInteger();

  // Barrier action runs ONCE per trip, on the last-arriving thread,
  // before any worker is released — the safe place to merge results.
  static final CyclicBarrier barrier = new CyclicBarrier(PARTIES, () ->
    System.out.println("  [merge] round total = " + roundSum.getAndSet(0)));

  static void worker(int id) {
    for (int round = 1; round <= ROUNDS; round++) {
      roundSum.addAndGet(id);                       // this thread's partial work
      System.out.println("W" + id + " finished round " + round);
      try {
        int idx = barrier.await();                  // park until all 3 arrive
        if (idx == 0) System.out.println("W" + id + " was the tripper");
      } catch (InterruptedException | BrokenBarrierException e) {
        // barrier is now BROKEN for everyone: stop, do not loop again
        Thread.currentThread().interrupt();
        return;
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread[] ts = new Thread[PARTIES];
    for (int i = 0; i < PARTIES; i++) {
      final int id = i + 1;
      ts[i] = new Thread(() -> worker(id), "W" + id);
      ts[i].start();
    }
    for (Thread t : ts) t.join();                   // <-- the fix: wait for all
    System.out.println("all rounds done");
  }
}

With ids 1,2,3 each round's sum is 6, printed twice, and the barrier is reused without ever being re-created.

Why the naive version is wrong

The original main calls t1.start(); t2.start(); and returns. The two worker threads are non-daemon, so the JVM does technically wait for them here — but the instant you copy that shape into a context with daemon threads, a thread pool, or an early-returning method, main races ahead of the workers and you see truncated or missing output and no merged result. Always join() (or block on a Future/latch) so the launching thread observes completion. The naive snippet also never showed the reset, the action, or the broken state — the parts that make a barrier a barrier.

Go: there is no CyclicBarrier

Go's standard library ships sync.WaitGroup, which is a one-shot barrier (like a latch): one set of goroutines Add/Done, and waiters Wait until the count hits zero — it does not re-arm. To get the cyclic, all-arrive-then-all-leave behavior with a merge step, you build it from a channel and a per-round goroutine, or reuse a WaitGroup per phase:

package main

import (
	"fmt"
	"sync"
)

const parties, rounds = 3, 2

func main() {
	for round := 1; round <= rounds; round++ {
		var wg sync.WaitGroup
		var mu sync.Mutex
		sum := 0
		wg.Add(parties)
		for id := 1; id <= parties; id++ {
			go func(id int) {
				defer wg.Done()
				mu.Lock()
				sum += id // this goroutine's partial work
				mu.Unlock()
				fmt.Printf("W%d finished round %d\n", id, round)
			}(id)
		}
		wg.Wait()                 // barrier: block until all 3 arrive
		fmt.Printf("  [merge] round total = %d\n", sum) // runs once per round
	}
	fmt.Println("all rounds done")
}

The idiomatic Go pattern fuses the barrier with the merge: instead of one shared mutable object that resets, you create a fresh WaitGroup per round and let the launching goroutine do the merge after Wait() returns — no barrier action, no tripper thread, no broken state to reason about.

Where the two runtimes differ

Pitfalls

Takeaways


Sources: Oracle Java SE API docs for java.util.concurrent.CyclicBarrier and CountDownLatch (including the OpenJDK CyclicBarrier source — the count/Generation/nextGeneration()/breakBarrier() internals traced above); Brian Goetz et al., Java Concurrency in Practice (barriers vs latches, §5.5); the Go sync.WaitGroup documentation and The Go Programming Language (Donovan & Kernighan) on WaitGroup-based rendezvous and context cancellation. Re-authored and deepened for this guide: replaced the generic paragraph and non-joining hello-world with a traced count-down/trip mechanism, a reusable two-phase example, the BrokenBarrierException failure model, and a side-by-side Go version.

🤖 Don't fully get this? Learn it with Claude

Stuck on 5. Barriers? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **5. Barriers** (Concurrency) and want to truly understand it. Explain 5. Barriers from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **5. Barriers** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **5. Barriers** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **5. Barriers** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes