Backend Development 13 min read

Understanding Java Concurrency Utilities: CountDownLatch, CyclicBarrier, Semaphore, and Exchanger

This article explains four Java concurrency utilities—CountDownLatch, CyclicBarrier, Semaphore, and Exchanger—detailing their concepts, typical use cases, and providing complete code examples that demonstrate synchronization patterns such as thread coordination, resource limiting, and data exchange.

Top Architect
Top Architect
Top Architect
Understanding Java Concurrency Utilities: CountDownLatch, CyclicBarrier, Semaphore, and Exchanger

The author (CoderV) provides a practical tutorial on several Java concurrency utilities, illustrating their purpose, usage patterns, and sample code for multithreaded applications.

CountDownLatch

Explanation

CountDownLatch acts like a gate with N locks; the gate opens only after all N locks are released. It is useful when one thread must wait for several other threads to finish their work before proceeding.

Code

public class TestCountDownLatch {
    public static void main(String[] args) {
        // Need to wait for two threads, so the count is 2
        CountDownLatch latch = new CountDownLatch(2);
        // Thread 1 runs for 1 second
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("1号选手准备就绪!用时1秒!");
                latch.countDown();
            }
        }).start();
        
        // Thread 2 runs for 3 seconds
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2号选手准备就绪!用时3秒!");
                latch.countDown();
            }
        }).start();
        
        try {
            System.out.println("请1号选手和2号选手各就各位!");
            // Main thread waits until both worker threads finish
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // After both threads finish, main thread continues
        System.out.println("裁判发枪,1号选手和2号选手开跑!");
    }
}

Output

请1号选手和2号选手各就各位!
1号选手准备就绪!用时1秒!
2号选手准备就绪!用时3秒!
裁判发枪,1号选手和2号选手开跑!

If the latch is removed, the main thread would fire the "gun" before the worker threads are ready, leading to out‑of‑order execution.

CyclicBarrier

Explanation

CyclicBarrier works like a fence that makes a set of threads wait for each other. Unlike CountDownLatch, each participating thread calls await() and waits for the others; once all have arrived, they are released together and the barrier can be reused.

Code

public class TestCyclicBarrier {
    // 1号选手跑的轮数
    public static int countA = 1;
    // 2号选手跑的轮数
    public static int countB = 1;
    public static void main(String[] args) {
        // 2 threads need to wait for each other
        CyclicBarrier barrier = new CyclicBarrier(2);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // Run three rounds
                for (int i = 0; i < 3; i++) {
                    System.out.println("1号选手开始跑!当前第" + countA++ + "轮比赛!");
                    // 1号选手跑得慢,每次跑三秒
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println("1号选手抵达终点!");
                        // Wait for the other thread
                        barrier.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                // Run three rounds
                for (int i = 0; i < 3; i++) {
                    System.out.println("2号选手开始跑!当前第" + countB++ + "轮比赛!");
                    // 2号选手跑得快,每次跑一秒
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println("2号选手抵达终点!");
                        barrier.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

Output

1号选手开始跑!当前第1轮比赛!
2号选手开始跑!当前第1轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第2轮比赛!
2号选手开始跑!当前第2轮比赛!
2号选手抵达终点!
1号选手抵达终点!
1号选手开始跑!当前第3轮比赛!
2号选手开始跑!当前第3轮比赛!
2号选手抵达终点!
1号选手抵达终点!

Without the barrier, the faster thread would finish all rounds before the slower one, breaking the intended coordination.

Semaphore

Explanation

A Semaphore controls access to a limited number of permits. Threads call acquire() to obtain a permit (blocking if none are available) and release() to return it, making it useful for limiting concurrent resource usage.

Code

public class TestSemaphore {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        System.out.println("顾客在售票处等候中");
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (;;) {
                    try {
                        Thread.sleep(500);
                        // Wait for a ticket
                        semaphore.acquire();
                        System.out.println("顾客拿到门票入场!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    try {
                        Thread.sleep(1000); // One hour per batch
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("售票处第" + (i + 1) + "小时售出两张票!");
                    // Release two permits each hour
                    semaphore.release();
                    semaphore.release();
                }
            }
        }).start();

        System.out.println("售票处开始售票!");
    }
}

Output

顾客在售票处等候中...
售票处开始售票!
售票处第1小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!
售票处第2小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!
售票处第3小时售出两张票!
顾客拿到门票入场!
顾客拿到门票入场!

Exchanger

Explanation

An Exchanger provides a synchronization point where two threads can exchange data objects. It works like a two‑party CyclicBarrier but adds the ability to swap values between the participating threads.

Code

public class TestExchanger {
    public static void main(String[] args) {
        Exchanger
exchanger = new Exchanger<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                String weapon = "装备";
                System.out.println("我是卖家,我带着" + weapon + "过来了!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("卖家到达地图上交易地点");
                try {
                    System.out.println("我是卖家,换回了" + exchanger.exchange(weapon));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                String money = "一万游戏币";
                System.out.println("我是买家,我带着" + money + "过来了");
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("买家到达地图上交易地点");
                try {
                    System.out.println("我是买家,换回了" + exchanger.exchange(money));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

Output

我是卖家,我带着装备过来了!
我是买家,我带着一万游戏币过来了
卖家达到交易地点
买家到达交易地点
我是买家,换回了装备
我是卖家,换回了一万游戏币

These examples demonstrate common synchronization scenarios in Java multithreading, helping developers choose the appropriate utility for thread coordination, resource throttling, and data exchange.

JavaConcurrencyMultithreadingSemaphoreCountDownLatchCyclicBarrierExchanger
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.