CountDownLatch和Semaphore:并发工具面试题深度拆解
大家好,今天咱们来聊聊面试中高频出现的并发工具:CountDownLatch 和 Semaphore。这两个家伙都属于java.util.concurrent包里的“利器”,名字听着唬人,但理解了机制其实很清晰。面试官最爱问它们的区别和适用场景,搞明白了,能让你在并发题上轻松过关!

2025年Java面试宝典重磅分享:
链接: https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g 提取码: 9b3g
CountDownLatch:一次性的“倒计时门闩”
想象一个场景:你组织了一场王者荣耀开黑,需要等5个队友都确认“准备好了”,才能一起点“开始匹配”。CountDownLatch 干的就是这个协调的活儿!
- 核心机制: 初始化时设定一个计数器(比如
new CountDownLatch(5))。每当一个任务完成,就调用countDown(),计数器减1。调用await()的线程(比如主线程)会阻塞,直到计数器减到0才放行。 - 关键特点:
- 一次性: 计数器归零后,再调用
await()会立刻放行,无法重置复用。想再用?new一个新的。 - 主等子: 通常是主线程(或协调者)在
await(),等待一组工作线程完成任务(调用countDown())。 - 不可增加: 计数器只能减,不能加。
- 一次性: 计数器归零后,再调用
- 典型面试场景:
- “启动服务时,如何确保所有依赖的组件都初始化完成再对外提供服务?”
- “多线程计算一个大任务,如何等所有子任务都算完再汇总结果?”
- “模拟并发压测,怎么让所有用户线程在同一时刻开始发起请求?”(主线程调用
countDownLatch.countDown()作为发令枪)
// 伪代码口述思路:
// 1. main线程创建CountDownLatch(5)
// 2. 启动5个工作线程,每个线程干完活就latch.countDown()
// 3. main线程调用latch.await(),等5个都完成
// 4. 5个都countDown后,main线程解除阻塞,继续执行汇总逻辑
Semaphore:灵活的“资源许可证管理器”
再想象一个场景:小区里只有3个充电桩,却有10辆电动车要充电。Semaphore 就是管理这3个充电桩的“物业”。
- 核心机制: 初始化时指定许可证(permits)数量(比如
new Semaphore(3))。线程想访问共享资源,必须先调用acquire()申请一个许可证(如果没有空闲的,线程就阻塞等待)。用完后,必须调用release()归还许可证,以便其他线程使用。 - 关键特点:
- 可复用: 许可证可以循环使用(acquire -> release -> acquire -> release ...)。
- 控制并发量: 核心功能!限制同时访问某一资源的线程数。比如数据库连接池、限流。
- 可增减: 初始化后,可以通过
release()增加许可(虽然不常用),也可以通过构造方法或reducePermits减少(需小心)。 - 公平/非公平: 构造方法可以选择是否是公平锁(影响线程获取许可证的顺序)。
- 典型面试场景:
- “如何实现一个线程安全的、固定大小的数据库连接池?”
- “系统接口要限流,每秒最多处理100个请求,怎么实现?”
- “有10个打印任务,但只有2台打印机,如何控制?”
- “对比
Semaphore和线程池,它们都能限制并发数,区别在哪?”(线程池管理的是工作线程本身的生命周期和任务队列;Semaphore只控制并发访问资源的入口数量,不管理线程。)
// 伪代码口述思路(连接池):
// 1. 初始化Semaphore(10) // 假设10个连接
// 2. 线程想获取连接:先semaphore.acquire() // 申请许可证,没许可证就等
// 3. 拿到许可证后,从池里取一个真实连接用
// 4. 用完连接,放回池里
// 5. 调用semaphore.release() // 归还许可证,让其他线程能用

核心差异一目了然
| 特性 | CountDownLatch | Semaphore |
| :----------- | :-------------------------- | :---------------------------- |
| 核心目的 | 等待事件完成 | 控制并发访问资源量 |
| 计数器 | 减到0触发 | 许可证数量 (>=0) |
| 重置性 | 不可重置 (一次性) | 可复用 (acquire/release) |
| 修改方向 | 只能减 (countDown) | 可增 (release) 可减 (acquire) |
| 主要用法 | 主线程 await() 等子线程 | 工作线程自己 acquire/release |
| 典型场景 | 初始化协调、多任务结果汇总 | 连接池、资源池、限流 |
简单记:
CountDownLatch是“等大家干完活开会”;Semaphore是“名额有限,先到先得,用完归还”。
面试官可能会怎么追问?
-
CountDownLatch能代替join()吗?为什么更好?- 能。比
join()更灵活,join()必须等线程结束,CountDownLatch只要任务完成就能countDown(),线程不一定结束。而且可以等待多个不同的线程组。
- 能。比
-
Semaphore的acquire()和release()数量必须严格匹配吗?- 强烈建议匹配! 不匹配会导致:
acquire()多了:许可证变负数,后续线程阻塞,可能死锁。release()多了:许可证总数变多,可能超过你期望的限制,导致过度并发。切记在finally块中释放!
- 强烈建议匹配! 不匹配会导致:
-
怎么用
Semaphore实现互斥锁 (Mutex)?- 初始化
Semaphore(1)。acquire()相当于lock(),release()相当于unlock()。这就是一个非公平的互斥锁。
- 初始化
-
CyclicBarrier和CountDownLatch有啥区别?CountDownLatch:主等子,一次性,计数器单向减。CyclicBarrier:子等子(所有线程互相等),可循环使用(reset()),计数器内部重置,到达屏障点可以执行一个可选任务(Runnable)。
面试技巧:如何清晰表达
被问到区别时,不要死背概念。结合具体例子:
“CountDownLatch 就像火箭发射前的检查,要等所有系统(引擎、导航、通信…)都回报‘Ready’(
countDown()),总控台(await())才点火发射。是一次性的协作。Semaphore 更像停车场入口的闸机,总共50个车位(
permits=50)。车来了要拿卡(acquire())才能进,没卡等;车走了要还卡(release())让别的车进。它控制的是同时停车的数量,闸机(Semaphore)可以一直用。”

用好并发工具,写出高效稳定的代码是面试加分项! 如果你正在准备面试,需要系统刷题和深入理解Java并发,可以考虑购买面试鸭会员获取海量真题解析和深度资料。对了,通过 面试鸭返利网 (mianshiyafanli.com) 找我购买,还能享受 25元返利 哦!
理解清楚 CountDownLatch 和 Semaphore 的设计思想和适用场景,下次遇到并发协调问题,你就能快速选出最合适的工具了!加油!


