一、CountDownLatch
Q1:什么是 CountDownLatch?
答: CountDownLatch 是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。
核心机制:
内部维护一个计数器(基于 AQS 的 state)
countDown():计数器减 1await():阻塞直到计数器为 0计数器归零后,不可重置
CountDownLatch latch = new CountDownLatch(3);
// 工作线程
new Thread(() -> {
doWork();
latch.countDown(); // 完成一个,减 1
}).start();
// 主线程
latch.await(); // 等待所有工作线程完成
System.out.println("全部完成");Q2:CountDownLatch 的底层原理是什么?
答: 基于 AQS 共享模式:
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count); // 初始计数
}
// 共享模式获取:state == 0 才成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 共享模式释放:CAS 减 1
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
}要点:
await()→acquireSharedInterruptibly(1)countDown()→releaseShared(1)state 为 0 时,唤醒所有等待线程
Q3:CountDownLatch 和 join() 有什么区别?
答:
// join:等线程结束
Thread t = new Thread(() -> doWork());
t.start();
t.join();
// CountDownLatch:等任务完成
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
doWork();
latch.countDown();
}).start();
latch.await();优势:CountDownLatch 更灵活,不必等线程结束。
Q4:CountDownLatch 能重复使用吗?
答: 不能。计数器归零后无法重置:
CountDownLatch latch = new CountDownLatch(1);
latch.countDown();
latch.await(); // 立即返回
latch.countDown(); // 无效果,state 已为 0需要重置:用 CyclicBarrier。
Q5:CountDownLatch 的典型应用场景?
答:
等待多个任务完成:
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
doWork();
latch.countDown();
}).start();
}
latch.await();并发测试:模拟高并发
CountDownLatch start = new CountDownLatch(1);
for (int i = 0; i < 100; i++) {
new Thread(() -> {
start.await(); // 同时起跑
doRequest();
}).start();
}
start.countDown();多阶段任务:等待初始化完成
Q6:CountDownLatch 的 await() 有超时版本吗?
答: 有:
boolean completed = latch.await(3, TimeUnit.SECONDS);
if (!completed) {
// 超时,做降级处理
}注意:超时后不会自动取消其他线程,需要自行处理。
二、CyclicBarrier
Q7:什么是 CyclicBarrier?
答: CyclicBarrier 让一组线程互相等待,直到所有线程都到达屏障点,再一起继续。
核心机制:
内部维护一个计数器(基于 ReentrantLock + Condition)
await():到达屏障,阻塞所有线程到达后,计数器重置,唤醒所有线程
可循环使用
CyclicBarrier barrier = new CyclicBarrier(3, () ->
System.out.println("所有线程到达"));
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("到达屏障");
barrier.await(); // 互相等待
System.out.println("继续执行");
}).start();
}Q8:CyclicBarrier 和 CountDownLatch 有什么区别?
答: 常考重点:
// CountDownLatch:主线程等子线程
latch.await();
// CyclicBarrier:子线程互相等
barrier.await();记忆:
CountDownLatch 是"等别人"
CyclicBarrier 是"互相等"
Q9:CyclicBarrier 的底层原理是什么?
答: 基于 ReentrantLock + Condition:
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
private final int parties; // 参与线程数
private int count; // 剩余未到达数
private Generation generation; // 代,用于重置
public int await() {
lock.lock();
try {
int index = --count;
if (index == 0) {
// 最后一个到达,执行回调,唤醒所有
nextGeneration();
return 0;
}
// 未到齐,等待
trip.await();
return index;
} finally {
lock.unlock();
}
}
}要点:
用
count记录剩余未到达线程最后一个到达时,调用
nextGeneration()重置并唤醒Generation用于区分"代",支持循环
Q10:CyclicBarrier 的 barrierAction 是什么?
答: 当所有线程到达屏障时,由最后一个到达的线程执行:
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达,执行汇总");
// 汇总数据、合并结果
});执行时机:
所有线程调用
await()后在唤醒其他线程之前
由最后一个到达的线程执行
用途:合并结果、记录日志、更新状态。
Q11:CyclicBarrier 如何实现循环复用?
答: 通过 nextGeneration() 重置:
private void nextGeneration() {
trip.signalAll(); // 唤醒所有等待线程
count = parties; // 重置计数
generation = new Generation(); // 新一代
}循环示例:
CyclicBarrier barrier = new CyclicBarrier(3);
for (int round = 0; round < 3; round++) {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
doWork();
barrier.await(); // 每轮都等待
}).start();
}
}关键:count 重置为 parties,generation 更新。
Q12:CyclicBarrier 的 BrokenBarrierException 是什么?
答: 当屏障被打破时抛出:
触发场景:
某个线程
await()时被中断某个线程
await()超时调用
reset()时其他线程正在等待
try {
barrier.await();
} catch (BrokenBarrierException e) {
// 屏障已破坏,需重置
}处理:
调用
barrier.reset()重置或重新创建 CyclicBarrier
Q13:CyclicBarrier 的 reset() 有什么作用?
答: 重置屏障到初始状态:
barrier.reset();
// 当前等待的线程抛 BrokenBarrierException
// 计数器重置为 parties注意:
正在等待的线程会收到异常
用于取消当前轮次
慎用,可能导致线程异常
Q14:CyclicBarrier 的典型应用场景?
答:
多阶段计算:
// 阶段1:所有线程计算
// 屏障
// 阶段2:所有线程汇总并行迭代:
for (int i = 0; i < rounds; i++) {
parallelWork();
barrier.await();
mergeResults();
}游戏加载:所有玩家加载完成才开始
三、Semaphore
Q15:什么是 Semaphore?
答: Semaphore(信号量)用于控制同时访问特定资源的线程数量。
核心机制:
内部维护许可数(permits)
acquire():获取许可,无许可则阻塞release():释放许可基于 AQS 共享模式
Semaphore semaphore = new Semaphore(3); // 最多 3 个并发
for (int i = 0; i < 10; i++) {
new Thread(() -> {
semaphore.acquire(); // 获取许可
try {
doWork(); // 最多 3 个线程同时执行
} finally {
semaphore.release(); // 释放许可
}
}).start();
}Q16:Semaphore 的底层原理是什么?
答: 基于 AQS 共享模式,state 表示可用许可数:
public class Semaphore {
abstract static class Sync extends AbstractQueuedSynchronizer {
// 获取许可:state 减
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 释放许可:state 加
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (compareAndSetState(current, next))
return true;
}
}
}
}要点:
acquire()→acquireSharedInterruptibly(1)release()→releaseShared(1)state < 0 时阻塞
Q17:Semaphore 的公平模式和非公平模式有什么区别?
答:
Semaphore fair = new Semaphore(3, true); // 公平
Semaphore unfair = new Semaphore(3, false); // 非公平(默认)非公平:新线程直接 CAS 抢许可,抢不到才排队。
Q18:Semaphore 的 acquire() 和 release() 必须配对吗?
答: 必须配对,否则许可泄漏:
semaphore.acquire();
try {
doWork();
} finally {
semaphore.release(); // 必须释放
}泄漏后果:
许可越来越少
最终无许可可用,线程永久阻塞
注意:release() 可以增加许可数,甚至超过初始值。
Q19:Semaphore 能实现互斥锁吗?
答: 能。许可数为 1 时,等价于互斥锁:
Semaphore mutex = new Semaphore(1);
mutex.acquire();
try {
// 临界区
} finally {
mutex.release();
}和 ReentrantLock 的区别:
Semaphore 不区分持有者
不可重入
更灵活(可释放多个许可)
Q20:Semaphore 的 tryAcquire() 有什么用?
答: 非阻塞获取许可:
// 立即返回
if (semaphore.tryAcquire()) {
try { doWork(); }
finally { semaphore.release(); }
} else {
// 获取失败,做其他事
}
// 带超时
if (semaphore.tryAcquire(1, TimeUnit.SECONDS)) {
try { doWork(); }
finally { semaphore.release(); }
}用途:限流、降级、避免阻塞。
Q21:Semaphore 和 ThreadPoolExecutor 有什么区别?
答:
// Semaphore:限制并发访问数据库
Semaphore dbLimit = new Semaphore(10);
// 线程池:限制线程数
ExecutorService pool = new ThreadPoolExecutor(...);配合使用:线程池 + Semaphore 限流。
Q22:Semaphore 的典型应用场景?
答:
限流:
Semaphore limit = new Semaphore(100);
limit.acquire();
try { handleRequest(); }
finally { limit.release(); }资源池:数据库连接池、对象池
互斥:许可为 1
控制并发度:限制同时执行的任务数
四、Exchanger
Q23:什么是 Exchanger?
答: Exchanger 用于两个线程之间交换数据:
线程 A 调用
exchange(dataA),阻塞线程 B 调用
exchange(dataB),阻塞两者都到达后,交换数据,继续执行
Exchanger<String> exchanger = new Exchanger<>();
// 线程 A
new Thread(() -> {
String data = "A的数据";
String received = exchanger.exchange(data);
System.out.println("A 收到: " + received);
}).start();
// 线程 B
new Thread(() -> {
String data = "B的数据";
String received = exchanger.exchange(data);
System.out.println("B 收到: " + received);
}).start();输出:
A 收到: B的数据
B 收到: A的数据Q24:Exchanger 的底层原理是什么?
答: 基于 CAS + 等待队列:
内部维护一个
Participant槽位第一个线程到达,把自己的数据放入槽位,等待
第二个线程到达,取出数据,放入自己的数据,唤醒第一个线程
两者交换完成
// 简化逻辑
public V exchange(V x) {
// 1. 尝试把 x 放入槽位
// 2. 如果有等待线程,交换并唤醒
// 3. 否则自己等待
}特点:
只支持两个线程交换
多线程需多次配对
Q25:Exchanger 的 exchange() 有超时版本吗?
答: 有:
try {
String received = exchanger.exchange(data,
3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 超时,对方未到达
}超时后:当前线程继续执行,但数据未交换。
Q26:Exchanger 支持多个线程吗?
答: 不直接支持。Exchanger 设计为两两交换:
多个线程调用
exchange(),会两两配对配对顺序不确定
适合生产者-消费者、遗传算法等场景
// 4 个线程,会两两交换
for (int i = 0; i < 4; i++) {
new Thread(() -> {
exchanger.exchange(data);
}).start();
}Q27:Exchanger 的典型应用场景?
答:
生产者-消费者:
// 生产者
Data data = produce();
Data received = exchanger.exchange(data);
// 消费者
Data data = consume();
Data received = exchanger.exchange(data);遗传算法:交换基因
管道处理:两个阶段交换数据
校对:两个线程交换结果比对
Q28:Exchanger 和 SynchronousQueue 有什么区别?
答:
// Exchanger:双向
String a = exchanger.exchange("A");
String b = exchanger.exchange("B");
// SynchronousQueue:单向
queue.put("data");
String s = queue.take();五、综合对比
Q29:四大并发工具的对比?
答:
Q30:如何选择并发工具?
答:
记忆:
等别人 → CountDownLatch
互相等 → CyclicBarrier
限并发 → Semaphore
换数据 → Exchanger
Q31:CountDownLatch 和 CyclicBarrier 能互相替代吗?
答: 不能完全替代:
CountDownLatch:一个线程等多个,不可复用
CyclicBarrier:多个线程互相等,可复用
场景区分:
// 主线程等 3 个子线程 → CountDownLatch
// 3 个子线程互相等 → CyclicBarrier但可以用 CyclicBarrier 模拟 CountDownLatch:
CyclicBarrier barrier = new CyclicBarrier(N + 1);
// 子线程 barrier.await()
// 主线程 barrier.await()Q32:Semaphore 能实现 CyclicBarrier 吗?
答: 可以,但麻烦:
// 用 Semaphore 模拟屏障
Semaphore semaphore = new Semaphore(0);
int parties = 3;
// 每个线程
semaphore.release(); // 到达
semaphore.acquire(parties); // 等待所有到达不推荐:CyclicBarrier 更语义化、更简洁。
Q33:这些工具的底层都是 AQS 吗?
答: 不全是:
注意:CyclicBarrier 用的是 Lock,不是直接继承 AQS。
Q34:这些工具是线程安全的吗?
答: 都是线程安全的:
CountDownLatch:AQS 保证
CyclicBarrier:ReentrantLock 保证
Semaphore:AQS 保证
Exchanger:CAS 保证
但使用时注意:
Semaphore 的 acquire/release 要配对
CyclicBarrier 异常后要 reset
Q35:如何在生产中监控这些工具?
答:
CountDownLatch:
latch.getCount(); // 剩余计数CyclicBarrier:
barrier.getNumberWaiting(); // 等待线程数
barrier.getParties(); // 总参与数
barrier.isBroken(); // 是否损坏Semaphore:
semaphore.availablePermits(); // 可用许可
semaphore.getQueueLength(); // 等待队列长度Exchanger:无直接监控方法
六、实战与踩坑
Q36:CountDownLatch 的 countDown() 忘记调用会怎样?
答: await() 会永久阻塞:
CountDownLatch latch = new CountDownLatch(3);
// 只 countDown 2 次
latch.await(); // 永远阻塞解决:
用超时
await(timeout)确保 finally 中 countDown
监控计数
Q37:CyclicBarrier 的 await() 抛异常后怎么办?
答:
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
barrier.reset(); // 重置屏障
// 或重新创建
}注意:异常后屏障损坏,其他等待线程也会抛异常。
Q38:Semaphore 的 release() 能超过初始许可吗?
答: 能。Semaphore 不检查上限:
Semaphore sem = new Semaphore(1);
sem.release(); // 许可变 2
sem.release(); // 许可变 3风险:
破坏限流语义
需自行保证 acquire/release 配对
Q39:CyclicBarrier 的 barrierAction 抛异常会怎样?
答: 屏障被打破:
其他等待线程抛
BrokenBarrierException屏障不可用,需 reset
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
throw new RuntimeException("汇总失败");
});建议:barrierAction 内 try-catch,避免影响屏障。
Q40:如何在并发测试中使用 CountDownLatch?
答: 模拟高并发:
int threadCount = 100;
CountDownLatch start = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
start.await(); // 同时起跑
doRequest();
} finally {
end.countDown();
}
}).start();
}
start.countDown(); // 发令
end.await(); // 等所有完成效果:100 个线程同时发起请求,模拟真实并发。
评论