CountDownLatch and Semaphore

Multithreading · lesson 13 of 38 · 3 min read

Wait for a batch of work to finish, and cap how many threads may enter a section at once.

Open this lesson in the learning hub

Key points

  • CountDownLatch is a one-shot gate. await() blocks until countDown() has driven the count to zero.
  • It never resets. If you need the same barrier round after round, that is CyclicBarrier.
  • Always countDown() in a finally. One failing task that skips it hangs every waiter.
  • Semaphore hands out N permits: acquire() takes one, release() gives it back. Ideal for connection caps and rate limits.
  • A Semaphore(1) behaves like a lock but has no owner, so any thread can release it. That is occasionally useful and often a bug.

Example

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(6);
        CountDownLatch finished = new CountDownLatch(6);   // counts down to zero, once
        Semaphore permits = new Semaphore(2);              // at most 2 inside at a time
        AtomicInteger inside = new AtomicInteger();
        AtomicInteger peak = new AtomicInteger();

        for (int i = 0; i < 6; i++) {
            pool.execute(() -> {
                try {
                    permits.acquire();
                    try {
                        peak.accumulateAndGet(inside.incrementAndGet(), Math::max);
                        Thread.sleep(50);
                        inside.decrementAndGet();
                    } finally { permits.release(); }       // release in finally, always
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally { finished.countDown(); }
            });
        }

        finished.await();                                  // wait for all six
        System.out.println("all 6 workers done");
        System.out.println("peak concurrency: " + peak.get() + " (semaphore allows 2)");
        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
    }
}

Latch waits for work to finish. Semaphore limits who gets in.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Multithreading course, and every lesson in it is listed on the Multithreading contents page.