本文涵盖 CountDownLatch 原理、使用场景、Java 示例、泳道图、可视化动画模拟,一篇文章看完就不会再踩坑。
标签:Java, CountDownLatch, 并发编程, 多线程同步, 生产者消费者, 并发可视化, HTML动画, 可视化编程
1. 什么是 CountDownLatch?
CountDownLatch 是 Java 并发包(java.util.concurrent)中的一个同步工具类,它允许一个或多个线程等待,直到一组操作完成。
- 核心思想:维护一个倒计数(Count),线程可以调用 
countDown()来递减计数。当计数到达 0 时,等待的线程会被唤醒。 - 
使用场景:
- 主线程等待多个子线程执行完毕(类似批处理任务)
 - 多个线程等待某个条件满足后再一起执行(类似起跑枪)
 - 控制多个阶段的同步执行
 
 
2. CountDownLatch 关键 API
CountDownLatch latch = new CountDownLatch(3); // 初始计数为3
latch.countDown();  // 计数减1
latch.await();      // 等待直到计数为0
CountDownLatch(int count):初始化计数器countDown():执行完一个任务后调用,计数器减1await():阻塞当前线程直到计数器为0
3. 实战场景:猴子抢香蕉 🍌
我们用一个有趣的例子来说明:
有一个容量为 20 的篮子,每次随机放 3~5 根香蕉,有三只猴子,每次取 1~3 根香蕉,取完后进入下一轮。
3.1 Java 实现
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class MonkeyBanana {
    static int bananas = 0;
    static final int MAX_CAPACITY = 20;
    static final Random rand = new Random();
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            // 放香蕉
            int put = rand.nextInt(3) + 3; // 3~5
            bananas = Math.min(MAX_CAPACITY, bananas + put);
            System.out.println("🍌 生产者放了 " + put + " 根香蕉,当前有 " + bananas + " 根");
            // 三只猴子取香蕉
            CountDownLatch latch = new CountDownLatch(3);
            for (int i = 1; i <= 3; i++) {
                final int id = i;
                new Thread(() -> {
                    int take = rand.nextInt(3) + 1; // 1~3
                    synchronized (MonkeyBanana.class) {
                        int actual = Math.min(bananas, take);
                        bananas -= actual;
                        System.out.println("🐒 猴子" + id + " 拿了 " + actual + " 根香蕉,剩余 " + bananas + " 根");
                    }
                    latch.countDown();
                }).start();
            }
            // 等猴子都取完
            latch.await();
            System.out.println("✅ 本轮结束,剩余香蕉:" + bananas + "\n");
            Thread.sleep(1000); // 下一轮
        }
    }
}
运行结果示例:
🍌 生产者放了 4 根香蕉,当前有 4 根
🐒 猴子1 拿了 2 根香蕉,剩余 2 根
🐒 猴子3 拿了 1 根香蕉,剩余 1 根
🐒 猴子2 拿了 1 根香蕉,剩余 0 根
✅ 本轮结束,剩余香蕉:0
3.2 泳道图版本(Mermaid)
sequenceDiagram
    participant Producer as 生产者
    participant Monkey1 as 猴子1
    participant Monkey2 as 猴子2
    participant Monkey3 as 猴子3
    participant Basket as 篮子
    Producer->>Basket: 放3~5根香蕉
    par 猴子1取香蕉
        Monkey1->>Basket: 取1~3根
    and 猴子2取香蕉
        Monkey2->>Basket: 取1~3根
    and 猴子3取香蕉
        Monkey3->>Basket: 取1~3根
    end
    Basket-->>Producer: CountDownLatch归零
    Producer->>Basket: 进入下一轮
3.3 时间线版本
t0: 生产者放香蕉
t1: 猴子1 开始取香蕉
t2: 猴子2 开始取香蕉
t3: 猴子3 开始取香蕉
t4: 所有猴子取完,CountDownLatch = 0
t5: 进入下一轮
3.4 浏览器可视化动画版本
我们可以用 HTML + JavaScript 做一个动态可视化模拟,保存为 banana_game.html,用浏览器打开即可运行:
点击展开可视化动画代码
```html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>香蕉篮子 - CountDownLatch 模拟</title>
<style>
    body { font-family: Arial, sans-serif; background: #f9f6ee; text-align: center; }
    h1 { color: #f4a261; }
    #basket { margin: 20px auto; width: 300px; height: 100px; background: #d4a373; border-radius: 15px; padding: 10px; }
    .banana { width: 20px; height: 50px; background: yellow; border-radius: 10px 10px 0 0; margin: 2px; display: inline-block; }
    .monkey { display: inline-block; width: 100px; margin: 20px; font-size: 20px; }
    .log { margin-top: 20px; height: 150px; overflow-y: auto; border: 1px solid #ccc; padding: 5px; background: white; }
</style>
</head>
<body>
    <h1>🍌 香蕉篮子 - CountDownLatch 动画模拟</h1>
    <div id="basket"></div>
    <div>
        <div class="monkey">🐒 猴子1</div>
        <div class="monkey">🐒 猴子2</div>
        <div class="monkey">🐒 猴子3</div>
    </div>
    <div class="log" id="log"></div>
<script>
let bananas = 0, maxCapacity = 20;
const logBox = document.getElementById("log");
function log(msg){ logBox.innerHTML += msg + "<br>"; logBox.scrollTop = logBox.scrollHeight; }
function renderBasket(){ const b = document.getElementById("basket"); b.innerHTML=""; for(let i=0;i<bananas;i++){ let banana=document.createElement("div"); banana.className="banana"; b.appendChild(banana);}}
async function run(){
    while(true){
        let put=Math.floor(Math.random()*3)+3;
        bananas=Math.min(maxCapacity,bananas+put);
        log(`🍌 生产者放了 ${put} 根香蕉,当前有 ${bananas} 根`); renderBasket();
        let promises=[];
        for(let i=1;i<=3;i++){
            promises.push(new Promise(r=>{
                setTimeout(()=>{
                    let take=Math.floor(Math.random()*3)+1;
                    let actual=Math.min(bananas,take);
                    bananas-=actual;
                    log(`🐒 猴子${i} 拿了 ${actual} 根香蕉,剩余 ${bananas} 根`);
                    renderBasket();
                    r();
                },Math.random()*2000+500);
            }));
        }
        await Promise.all(promises);
        log(`✅ 本轮结束,篮子还剩 ${bananas} 根香蕉\n`);
        await new Promise(r=>setTimeout(r,1000));
    }
}
run();
</script>
</body>
</html>
```
4. 总结
CountDownLatch适用于一次性倒计数的线程同步场景- 用于生产者消费者模式时,可以让生产者等待多个消费者完成
 - 
我们的猴子抢香蕉例子展示了:
- Java 多线程实现
 - 泳道图(Mermaid)
 - 时间线
 - 浏览器动画模拟
 
 
一句话记忆:CountDownLatch 就像“闸门”,关上后大家一起干活,等所有人完成后闸门才会打开。