它们不是锁,是调度器
锁解决「不能同时干」,这一篇的工具解决「什么时候一起干」——等待与配额。底层全是第 10 篇的 AQS 共享模式:state 存计数,tryAcquireShared 判定,传播唤醒接力放行。一个 AQS,三种人设。
CountDownLatch:一次性门闩
语义:计数器归零之前,所有 await 的线程原地等待;每次 countDown 减一。典型场景——主线程把任务拆给 N 个线程,等全部完工再汇总:
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
try { doQuery(); } finally { latch.countDown(); } // 干完减一
});
}
latch.await(); // 阻塞,直到三个都干完
mergeResults();两个注意:countDown 必须放 finally——任务抛异常也要减,否则永远等不到归零,主线程挂在 WAITING;不可重用,归零即报废。定位:「等多个固定事件」的一次性协调。
CyclicBarrier:人齐再走
语义反过来:N 个线程各自干到栅栏处 await,人齐了才一起放行,而且可循环重用(Cyclic 的本意)。适合多线程分阶段协作——各算各的,每轮结束对齐一次再进下一轮:
CyclicBarrier barrier = new CyclicBarrier(4, () ->
System.out.println("一轮计算完成,开始合并")); // 人齐后执行的回调
// 每个工作线程干完当前轮次:
barrier.await(); // 等其他三个到齐
computeNextPhase();与 CDL 的本质区别:CDL 等的是「事件完成」——事件自己调 countDown,和等待者没关系;Barrier 等的是「同伴到齐」——大家互相等。另外 Barrier 有 broken 机制:某个线程等待中被中断,整轮栅栏作废(BrokenBarrierException),不会出现一部分人过了栅栏、另一部分还挂在原地的不一致。
Semaphore:配额发放
语义:state 是剩余许可,acquire 拿一个(没有就排队),release 还一个。两大场景:限流——只放 N 个线程同时进;资源池——连接池、对象池的经典实现骨架:
Semaphore permits = new Semaphore(10); // 最多 10 个并发
public Data query() throws InterruptedException {
permits.acquire(); // 拿许可,没有就排队
try { return doQuery(); }
finally { permits.release(); } // 用完必须还
}tryAcquire(3, TimeUnit.SECONDS) 支持超时;构造第二个参数传 true 开公平模式。和第 8 篇呼应一下:统计类计数用 LongAdder,限流配额这种要精确控制进出的,用 Semaphore——两者看似都是数字增减,语义完全不同。
一句话记住三件套
CDL 等事件(一次性)、Barrier 等同伴(可循环)、Semaphore 管配额(可增减)。API 都很简单,难点从来不在用法,而在识别场景——先问自己:我是在等事件、等人齐,还是在控数量?对号入座。下一篇解决更高级的问题:多个异步任务怎么优雅地编排——CompletableFuture。
评论 (0)