Java Concurrency
Study Notes: Semaphore & CountDownLatch
[Link] | Thread Safety | Beginner-Friendly
Chapter 1 — Semaphore
What is a Semaphore?
A Semaphore is a concurrency tool that controls how many threads can access a shared resource at the same
time. Think of it like a bouncer at a club with a fixed capacity — only N people are allowed inside at once. When
someone leaves, the next person in line can enter.
It works with an internal permit counter:
• acquire() — Takes a permit (decrements the counter). If no permit is available, the thread waits.
• release() — Returns a permit (increments the counter), allowing one waiting thread to proceed.
■ Real-world analogy: A computer lab with only 3 computers and 5 students. The lab monitor holds
3 keys. Students who get a key use a computer; others wait outside until a key is returned.
Import Used
import [Link];
This is a built-in Java class — no external library needed. It lives inside [Link], the standard
package for multi-threading tools introduced in Java 5.
Full Code
import [Link];
public class SemaphoreDemo {
// Only 3 threads can access the resource at once
private static Semaphore bouncer = new Semaphore(3);
static class Person extends Thread {
String name;
Person(String name) { [Link] = name; }
@Override
public void run() {
try {
[Link](name + " is waiting for a computer...");
[Link](); // Block if no permits
[Link](name + " got a computer!");
[Link](2000); // Use computer 2 sec
[Link](name + " is leaving.");
} catch (InterruptedException e) {
[Link]();
} finally {
[Link](); // Always return permit
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
new Person("Person " + i).start();
Step-by-Step Explanation
Step 1 — Create the Semaphore
private static Semaphore bouncer = new Semaphore(3);
Keyword Meaning
private Only this class can use it
static Shared by ALL threads — one bouncer for everyone
Semaphore The data type
bouncer Our variable name
new Semaphore(3) Start with 3 permits (3 computers available)
Step 2 — Person extends Thread
static class Person extends Thread { ... }
By writing extends Thread, each Person becomes its own independent thread. This means multiple Persons
can run at the same time — just like multiple students walking into the lab simultaneously.
Step 3 — acquire() — The Gate
[Link]();
• If a permit is available → thread gets it and moves forward instantly.
• If no permits left → thread stops and waits here until one is released.
• This is what causes Person 4 and 5 to wait while 1, 2, 3 use computers.
Step 4 — [Link]() — Simulating Work
[Link](2000); // 2000 ms = 2 seconds
Pauses the thread for 2 seconds to simulate real work (reading a file, calling an API, etc.).
Step 5 — finally + release() — The Exit
finally {
[Link]();
The finally block always runs — even if an exception is thrown. Putting release() here guarantees the permit is
returned no matter what. If we forgot this, waiting threads would be stuck forever (a deadlock).
Step 6 — main() — Launching 5 Threads
for (int i = 1; i <= 5; i++) {
new Person("Person " + i).start();
Creates and starts 5 Person threads simultaneously. The first 3 acquire permits right away; the remaining 2
block on acquire() until a permit is freed.
Visual Flow
Computers (max 3): [ P1 ][ P2 ][ P3 ] <- using computers
Waiting: P4, P5 <- blocked on acquire()
P1 finishes -> release() -> counter: 2->3
[ P4 ][ P2 ][ P3 ] <- P4 unblocks
P5 <- still waiting
P2 finishes -> release() -> counter: 2->3
[ P4 ][ P5 ][ P3 ] <- P5 unblocks
(no one left waiting)
Quick Reference Table
Method / Concept What it does
new Semaphore(N) Create semaphore allowing N concurrent threads
acquire() Enter — decrement counter, wait if 0
release() Exit — increment counter, wake a waiting thread
finally block Ensures release() is ALWAYS called
extends Thread Makes the class run as an independent thread
Chapter 2 — CountDownLatch
What is a CountDownLatch?
A CountDownLatch is a concurrency tool that makes one (or more) threads wait until a set of other threads
have all finished their work. It works like a countdown timer — it starts at N and counts down to 0. When it hits
0, the waiting thread wakes up and continues.
■■ Real-world analogy: A manager waiting for 3 workers to prepare a construction site. The
manager cannot start building until ALL 3 workers are done with their preparations.
Semaphore vs CountDownLatch — Key Difference
Feature Semaphore vs CountDownLatch
Purpose Semaphore: LIMIT threads at once | Latch: WAIT for threads to finish
Analogy Semaphore: Bouncer with tokens | Latch: Manager waiting for workers
Main methods Semaphore: acquire() / release() | Latch: await() / countDown()
Reusable? Semaphore: Yes | Latch: No (one-time use)
Who waits? Semaphore: Incoming threads | Latch: The controlling thread
Import Used
import [Link];
Also built-in to [Link] — no external libraries needed.
Full Code
import [Link];
public class LatchDemo {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3); // counter starts at 3
for (int i = 1; i <= 3; i++) {
new Thread(new Worker(i, latch)).start(); // start 3 workers
}
try {
[Link]("MANAGER: Waiting for workers...");
[Link](); // STOP until counter = 0
[Link]("MANAGER: All done. Starting Project!");
} catch (InterruptedException e) {
[Link]();
static class Worker implements Runnable {
private int id;
private CountDownLatch latch;
Worker(int id, CountDownLatch latch) {
[Link] = id;
[Link] = latch;
@Override
public void run() {
try {
[Link]("Worker " + id + " is doing a task...");
[Link](2000); // simulate 2s of work
[Link]("Worker " + id + " is done.");
[Link](); // reduce counter by 1
} catch (InterruptedException e) {
[Link]();
Step-by-Step Explanation
Step 1 — Create the Latch
CountDownLatch latch = new CountDownLatch(3);
Creates a latch with its counter set to 3. Think of it as a checklist with 3 items that must all be ticked before work
can begin.
Counter: [3] -> Worker 1 done -> [2] -> Worker 2 done -> [1] -> Worker 3 done -> [0]
When the counter reaches 0, the waiting manager thread wakes up.
Step 2 — implements Runnable vs extends Thread
Approach How threads are created
extends Thread class IS a thread. Start with: new Person().start()
implements Runnable class is a TASK. Wrap it: new Thread(new Worker()).start()
Both are valid. implements Runnable is generally preferred because Java only allows one class to be
extended, so using Runnable keeps your options open.
Step 3 — Passing the Latch to Workers
Worker(int id, CountDownLatch latch) {
[Link] = id;
[Link] = latch;
The same latch object is passed to all 3 workers and the manager. They all look at the same counter — this
shared reference is what makes coordination possible.
Step 4 — await() — The Manager Waits
[Link]();
• The manager thread STOPS here and does nothing.
• It will only continue when the latch counter reaches exactly 0.
• Think of it as: "I'll wait right here until everyone checks in."
Step 5 — countDown() — Workers Signal Completion
[Link]();
• Each worker calls this after finishing its task.
• This reduces the latch counter by 1.
• When all 3 workers call it, counter hits 0 → manager wakes up.
• Important: countDown() never blocks — it just decrements and continues.
Step 6 — try-catch for InterruptedException
try { ... }
catch (InterruptedException e) { [Link](); }
Both await() and [Link]() can throw InterruptedException if the thread is forcefully stopped. The catch
block handles this gracefully by printing the error details.
Visual Flow
main() starts
+-- Latch created: counter = 3
+-- Worker 1 started (runs independently)
+-- Worker 2 started (runs independently)
+-- Worker 3 started (runs independently)
+-- Manager hits [Link]() --> STOPS and waits
Worker 1 -> works 2s -> countDown() -> counter: 3->2
Worker 2 -> works 2s -> countDown() -> counter: 2->1
Worker 3 -> works 2s -> countDown() -> counter: 1->0
counter = 0 --> Manager WAKES UP
Manager prints "All done. Starting Project!"
Program ends
Important Limitation
■■ CountDownLatch is NOT reusable. Once the counter reaches 0, it stays at 0 forever. If you
need a latch that can reset and be used again, Java provides CyclicBarrier — a similar tool that can
be reused multiple times.
Feature CountDownLatch vs CyclicBarrier
Reusable? CountDownLatch: No | CyclicBarrier: Yes
Who waits? CountDownLatch: One thread waits for many | CyclicBarrier: All threads wait for each other
Use case CountDownLatch: Manager waits for workers | CyclicBarrier: All runners wait at starting line
Quick Reference Table
Method / Concept What it does
new CountDownLatch(N) Create a latch with counter starting at N
await() Block the current thread until counter reaches 0
countDown() Reduce the counter by 1 (never blocks)
implements Runnable Alternative to extends Thread; wraps task in new Thread()
[Link] = latch Store shared latch reference in each worker
Chapter 3 — Comparison & Summary
Side-by-Side Comparison
Topic Semaphore vs CountDownLatch
Goal Limit simultaneous access vs Wait for completion
Counter goes Up and Down (permits in/out) vs Only Down (0 and done)
Who blocks? Incoming threads block vs The waiting manager blocks
Reusable? Yes vs No
Key methods acquire() / release() vs await() / countDown()
Analogy Bouncer with N tokens vs Manager with N-item checklist
When to Use Which?
Situation Use
Limit DB connections to max 5 at once Semaphore(5)
Wait for 3 services to start before app boots CountDownLatch(3)
Allow only 2 threads to write a file at once Semaphore(2)
Wait for all workers to finish before reporting CountDownLatch(N)
Control access to a shared printer pool Semaphore(printerCount)
Key Java Concepts Covered
Concept Explanation
Thread An independent unit of execution running alongside others
extends Thread Makes a class into a thread directly
implements Runnable Defines a task; must be wrapped in new Thread()
start() Launches the thread; internally calls run()
run() The code the thread actually executes
[Link](ms) Pauses the current thread for given milliseconds
try-catch-finally Error handling; finally always runs
static Shared across all instances/threads
this.x = x Assigns a constructor parameter to the object's own field
Quick Cheat Sheet
SEMAPHORE COUNTDOWNLATCH
--------- --------------
new Semaphore(N) new CountDownLatch(N)
-> N permits available -> Counter starts at N
[Link]() [Link]()
-> Get a permit -> Block until counter == 0
-> Block if none available
[Link]() [Link]()
-> Return a permit -> Reduce counter by 1
-> Wake a waiting thread -> Never blocks
Use: limit concurrency Use: coordinate completion
■ You have now learned: Semaphore (limit concurrent access) and CountDownLatch (wait for all
tasks to complete) — two of the most important building blocks in Java multi-threaded programming.