Synchronizers in Java
Synchronizers are classes in java.util.concurrent that coordinate threads: they make threads wait until something has happened, meet at a common point, or take turns using a limited resource. They replace most hand-written wait/notify code. The five synchronizers are CountDownLatch, CyclicBarrier, Semaphore, Phaser and Exchanger. In this lesson, you will learn what each one does, with an example of each.
The Five Synchronizers
| Class | What it does | Reusable |
|---|---|---|
CountDownLatch | Threads wait until a count reaches zero | No |
CyclicBarrier | A fixed group of threads waits until all arrive, then all continue | Yes |
Semaphore | Allows at most N threads to use a resource at a time | Yes |
Phaser | Like a barrier with many phases and a changing number of parties | Yes |
Exchanger | Two threads swap objects at a meeting point | Yes |
Example: CountDownLatch
CountDownLatch starts with a count. Threads call countDown(); threads that call await() wait until the count reaches zero.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LatchDemo {
public static void main(String[] args) throws InterruptedException {
String[] services = {"database", "cache", "email"};
CountDownLatch ready = new CountDownLatch(services.length);
try (ExecutorService pool = Executors.newFixedThreadPool(3)) {
for (String s : services) {
pool.submit(() -> {
sleep(100);
ready.countDown();
});
}
ready.await();
System.out.println("All " + services.length
+ " services started; Navika's app opens");
}
}
static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}All 3 services started; Navika's app opensExplanation: The main thread waits on ready.await() until each of the three startup tasks has called countDown(). A latch is used once; it can't be reset.
Example: CyclicBarrier and Semaphore
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
public class BarrierSemaphore {
public static void main(String[] args) {
AtomicInteger rounds = new AtomicInteger();
CyclicBarrier barrier = new CyclicBarrier(3,
() -> System.out.println("Round " + rounds.incrementAndGet()
+ ": all three runners arrived"));
try (ExecutorService pool = Executors.newFixedThreadPool(3)) {
for (int r = 0; r < 3; r++) {
pool.submit(() -> {
for (int lap = 0; lap < 2; lap++) {
try {
barrier.await();
} catch (Exception e) {
return;
}
}
});
}
}
Semaphore rooms = new Semaphore(2);
AtomicInteger inside = new AtomicInteger();
AtomicInteger maxInside = new AtomicInteger();
try (ExecutorService pool = Executors.newFixedThreadPool(6)) {
for (int i = 0; i < 6; i++) {
pool.submit(() -> {
try {
rooms.acquire();
try {
maxInside.accumulateAndGet(
inside.incrementAndGet(), Math::max);
Thread.sleep(100);
inside.decrementAndGet();
} finally {
rooms.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
System.out.println("Most students in the study rooms at once: "
+ maxInside.get());
}
}Round 1: all three runners arrived
Round 2: all three runners arrived
Most students in the study rooms at once: 2Explanation: The barrier waits for three threads; when all have called await(), it runs its action once and releases them. It then resets itself, so the same barrier is used for the second lap. The semaphore has two permits: acquire takes one (waiting if none is free) and release returns it, so even with six threads, never more than two are inside at the same time.
Example: Phaser and Exchanger
import java.util.concurrent.Exchanger;
import java.util.concurrent.Phaser;
public class PhaserExchanger {
public static void main(String[] args) throws Exception {
Phaser phaser = new Phaser(1);
for (String name : new String[] {"Navika", "Srihitha"}) {
phaser.register();
new Thread(() -> {
phaser.arriveAndAwaitAdvance();
phaser.arriveAndDeregister();
}, name).start();
}
int phase = phaser.arriveAndAwaitAdvance();
System.out.println("Phase 0 done, now in phase " + phase);
phase = phaser.arriveAndAwaitAdvance();
System.out.println("Phase 1 done, parties left: "
+ phaser.getRegisteredParties());
phaser.arriveAndDeregister();
Exchanger<String> exchanger = new Exchanger<>();
Thread advitha = new Thread(() -> {
try {
String got = exchanger.exchange("notes from Advitha");
System.out.println("Advitha received: " + got);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
advitha.start();
String got = exchanger.exchange("notes from Navika");
advitha.join();
System.out.println("Navika received: " + got);
}
}Phase 0 done, now in phase 1
Phase 1 done, parties left: 1
Advitha received: notes from Navika
Navika received: notes from AdvithaExplanation: The phaser starts with the main thread registered and adds two workers. Everyone arrives at phase 0, and they move on together. In phase 1, the workers arrive and deregister, leaving only the main thread: unlike a barrier, the number of parties can change. The Exchanger makes two threads meet and swap objects.
Common Mistakes
Trying to reuse a CountDownLatch. Once it reaches zero, it stays open; use a CyclicBarrier or Phaser.
Forgetting release() after acquire(). Permits leak and threads eventually wait forever; release in finally.
A barrier party that never arrives. The others wait forever; use await with a timeout.
Interview Corner
What is the difference between CountDownLatch and CyclicBarrier?
CountDownLatch lets one or more threads wait until a count, decremented by countDown() from any threads, reaches zero; it's single-use and the counting threads don't wait. CyclicBarrier makes a fixed number of threads wait for each other at a common point; when the last arrives, an optional action runs and all are released, and the barrier resets for reuse. A latch waits for events; a barrier waits for threads.
Practice
- Easy: Use a
CountDownLatchso the main thread prints "Exam over" only after five student threads have finished. - Harder: Limit a "download" method to three simultaneous calls with a
Semaphore, and test it with ten threads.
Key Takeaways
Was this tutorial helpful?