Java并发编程三大法器:Semaphore、CyclicBarrier、CountDownLatch
在Java并发编程中,有三个非常重要的同步工具类,它们被称为"并发控制三大法器":Semaphore(信号量)、CyclicBarrier(循环屏障)和CountDownLatch(倒计时门闩)。这三个类都位于java.util.concurrent
包中,为多线程协调提供了强大的支持。
1. Semaphore(信号量)
1.1 概念介绍
Semaphore是一个计数信号量,从概念上讲,信号量维护了一组许可证(permits)。每个acquire()
方法会阻塞,直到有许可证可用,然后获取该许可证。每个release()
方法会添加一个许可证,可能会释放一个正在阻塞的获取者。
核心特点:
- 控制同时访问特定资源的线程数量
- 支持公平和非公平模式
- 可以一次获取/释放多个许可证
- 没有所有权概念,任何线程都可以释放许可证
1.2 使用示例
示例1:限流控制
public class RateLimiter {
private final Semaphore semaphore;
public RateLimiter(int maxConcurrent) {
this.semaphore = new Semaphore(maxConcurrent);
}
public void execute(Runnable task) {
try {
semaphore.acquire();
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
}
1.3 源码深度分析
1.3.1 核心架构设计
Semaphore基于AbstractQueuedSynchronizer(AQS)实现,采用共享模式来管理许可证。其核心设计包含以下几个关键组件:
/**
* Semaphore的核心同步器抽象类
* 继承自AQS,使用state字段存储许可证数量
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
/**
* 构造函数:初始化许可证数量
* @param permits 初始许可证数量,存储在AQS的state字段中
*/
Sync(int permits) {
setState(permits); // 将许可证数量设置到AQS的state字段
}
/**
* 获取当前可用许可证数量
* @return 当前state值,即可用许可证数量
*/
final int getPermits() {
return getState();
}
/**
* 非公平模式下尝试获取共享锁(许可证)
* 这是Semaphore性能优化的关键方法
* @param acquires 要获取的许可证数量
* @return 剩余许可证数量,负数表示获取失败
*/
final int nonfairTryAcquireShared(int acquires) {
for (;;) { // 自旋CAS操作,保证原子性
int available = getState(); // 获取当前可用许可证数量
int remaining = available - acquires; // 计算获取后剩余数量
// 关键判断:如果剩余数量<0(许可证不足)或者CAS更新成功,则返回
if (remaining < 0 || // 许可证不足,直接返回负数
compareAndSetState(available, remaining)) // CAS更新state
return remaining; // 返回剩余许可证数量
// 如果CAS失败,继续自旋重试
}
}
/**
* 释放共享锁(归还许可证)
* @param releases 要释放的许可证数量
* @return true表示释放成功,会唤醒等待线程
*/
protected final boolean tryReleaseShared(int releases) {
for (;;) { // 自旋CAS操作
int current = getState(); // 获取当前许可证数量
int next = current + releases; // 计算释放后的数量
// 溢出检查:防止许可证数量超过Integer.MAX_VALUE
if (next < current)
throw new Error("Maximum permit count exceeded");
// CAS更新state,成功则返回true
if (compareAndSetState(current, next))
return true; // 返回true会触发AQS唤醒等待线程
}
}
/**
* 减少许可证数量(内部方法)
* @param reductions 要减少的数量
*/
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // 下溢检查
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
/**
* 清空所有许可证(内部方法)
* @return 清空前的许可证数量
*/
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
1.3.2 公平与非公平实现对比
非公平同步器(NonfairSync):
/**
* 非公平同步器:性能优先,允许"插队"
* 新来的线程可能比等待队列中的线程更早获得许可证
*/
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
/**
* 直接调用非公平获取方法,不检查等待队列
*/
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
公平同步器(FairSync):
/**
* 公平同步器:严格按照FIFO顺序分配许可证
* 防止线程饥饿,但性能略低于非公平模式
*/
static final class FairSync extends Sync {
FairSync(int permits) {
super(permits);
}
/**
* 公平模式下的获取方法
* 必须先检查是否有前驱节点在等待
*/
protected int tryAcquireShared(int acquires) {
for (;;) {
// 关键:先检查是否有前驱节点在等待
if (hasQueuedPredecessors()) // AQS提供的方法,检查等待队列
return -1; // 有前驱节点,获取失败,当前线程需要排队
// 没有前驱节点,尝试获取许可证(与非公平模式相同)
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
1.3.3 核心方法调用链路分析
acquire()方法完整调用链:
// 1. 用户调用acquire()
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1); // 委托给AQS的共享模式获取方法
}
// 2. AQS的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) // 检查中断状态
throw new InterruptedException();
// 尝试获取共享锁,返回值>=0表示成功
if (tryAcquireShared(arg) < 0) // 调用Semaphore重写的tryAcquireShared
doAcquireSharedInterruptibly(arg); // 获取失败,进入等待队列
}
// 3. Semaphore重写的tryAcquireShared(以非公平为例)
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires); // 调用非公平获取方法
}
// 4. 如果获取失败,AQS的doAcquireSharedInterruptibly方法
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 创建共享模式节点并加入等待队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) { // 自旋等待
final Node p = node.predecessor();
if (p == head) { // 前驱是头节点,尝试获取
int r = tryAcquireShared(arg);
if (r >= 0) { // 获取成功
setHeadAndPropagate(node, r); // 设置新头节点并传播唤醒
p.next = null;
failed = false;
return;
}
}
// 检查是否需要阻塞,并处理中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
release()方法完整调用链:
// 1. 用户调用release()
public void release() {
sync.releaseShared(1); // 委托给AQS的共享模式释放方法
}
// 2. AQS的releaseShared方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 调用Semaphore重写的tryReleaseShared
doReleaseShared(); // 释放成功,唤醒等待线程
return true;
}
return false;
}
// 3. Semaphore重写的tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true; // 返回true触发唤醒操作
}
}
// 4. AQS的doReleaseShared方法
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 需要唤醒后继节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h); // 唤醒后继节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head) // 头节点没有变化,退出循环
break;
}
}
1.3.4 Semaphore工作流程图
flowchart TD
A["Thread calls acquire()"] --> B{"Check interrupt status"}
B -->|"Interrupted"| C["Throw InterruptedException"]
B -->|"Not interrupted"| D["Call tryAcquireShared()"]
D --> E{"Fair mode?"}
E -->|"Yes"| F{"Has queued predecessors?"}
E -->|"No"| G["Try acquire permits directly"]
F -->|"Yes"| H["Return -1, acquire failed"]
F -->|"No"| G
G --> I{"Permits available?"}
I -->|"Yes"| J["CAS update state"]
I -->|"No"| H
J --> K{"CAS success?"}
K -->|"Yes"| L["Acquire success, return remaining"]
K -->|"No"| G
H --> M["Add to wait queue"]
M --> N["Spin wait or block"]
N --> O{"Woken up?"}
O -->|"Yes"| D
O -->|"No"| P{"Check interrupt?"}
P -->|"Yes"| C
P -->|"No"| N
L --> Q["Method returns"]
R["Thread calls release()"] --> S["Call tryReleaseShared()"]
S --> T["CAS increment state"]
T --> U{"CAS success?"}
U -->|"Yes"| V["Call doReleaseShared()"]
U -->|"No"| T
V --> W["Wake up waiting threads"]
W --> X["release() returns"]
style A fill:#e1f5fe
style R fill:#e8f5e8
style C fill:#ffebee
style Q fill:#f3e5f5
style X fill:#f3e5f5
2. CyclicBarrier(循环屏障)
2.1 概念介绍
CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。屏障被称为"循环"的,因为它可以在等待的线程被释放之后重新使用。
核心特点:
- 让一组线程到达屏障点时被阻塞,直到最后一个线程到达
- 支持屏障动作(barrier action),在所有线程到达后执行
- 可以重复使用(循环性)
- 支持超时和中断
2.2 使用示例
示例1:并行计算
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;
public class ParallelCalculator {
private final int threadCount;
private final CyclicBarrier barrier;
private final double[][] matrix;
private final double[] result;
public ParallelCalculator(double[][] matrix) {
this.matrix = matrix;
this.threadCount = matrix.length;
this.result = new double[threadCount];
// 屏障动作:合并结果
Runnable barrierAction = () -> {
System.out.println("所有线程计算完成,开始合并结果");
double sum = 0;
for (double value : result) {
sum += value;
}
System.out.println("最终结果: " + sum);
};
this.barrier = new CyclicBarrier(threadCount, barrierAction);
}
public void calculate() {
for (int i = 0; i < threadCount; i++) {
final int row = i;
new Thread(() -> {
try {
// 计算当前行
double sum = 0;
for (double value : matrix[row]) {
sum += value;
}
result[row] = sum;
System.out.println("线程 " + row + " 计算完成");
// 等待其他线程
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
示例2:多阶段任务
public class MultiPhaseTask {
private final CyclicBarrier barrier;
private final int workerCount;
public MultiPhaseTask(int workerCount) {
this.workerCount = workerCount;
this.barrier = new CyclicBarrier(workerCount, () -> {
System.out.println("阶段完成,进入下一阶段");
});
}
public void start() {
for (int i = 0; i < workerCount; i++) {
final int workerId = i;
new Thread(() -> {
try {
for (int phase = 1; phase <= 3; phase++) {
System.out.println("Worker " + workerId + " 执行阶段 " + phase);
Thread.sleep(1000); // 模拟工作
barrier.await(); // 等待其他线程完成当前阶段
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
2.3 源码深度分析
2.3.1 核心架构设计
CyclicBarrier不同于Semaphore和CountDownLatch,它没有使用AQS,而是基于ReentrantLock和Condition实现。这种设计更适合循环使用的场景:
/**
* CyclicBarrier的核心实现
* 使用ReentrantLock保证线程安全,Condition实现线程等待/唤醒
*/
public class CyclicBarrier {
/** 用于保护屏障状态的锁 */
private final ReentrantLock lock = new ReentrantLock();
/** 等待条件:所有线程到达屏障点 */
private final Condition trip = lock.newCondition();
/** 参与屏障的线程总数(不变) */
private final int parties;
/** 屏障触发时执行的任务(可选) */
private final Runnable barrierCommand;
/** 当前代,用于实现循环功能 */
private Generation generation = new Generation();
/** 当前还在等待的线程数(递减计数器) */
private int count;
/**
* 代(Generation)的概念:
* 每次屏障被触发后,都会创建新的Generation
* 这样可以区分不同轮次的屏障使用,实现循环功能
*/
private static class Generation {
/** 屏障是否被破坏 */
boolean broken = false;
}
/**
* 构造函数
* @param parties 参与线程数
* @param barrierAction 屏障触发时执行的动作
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties; // 初始化等待计数
this.barrierCommand = barrierAction;
}
}
2.3.2 核心等待方法详解
dowait方法是CyclicBarrier的核心,处理所有等待逻辑:
/**
* 核心等待方法:处理线程在屏障点的等待逻辑
* @param timed 是否有超时限制
* @param nanos 超时时间(纳秒)
* @return 线程在屏障中的索引(最后到达的线程返回0)
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException, TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock(); // 获取锁,保证线程安全
try {
final Generation g = generation; // 记录当前代,用于后续检查
// 1. 检查屏障是否已被破坏
if (g.broken)
throw new BrokenBarrierException();
// 2. 检查线程中断状态
if (Thread.interrupted()) {
breakBarrier(); // 破坏屏障
throw new InterruptedException();
}
// 3. 递减等待计数,获取当前线程的索引
int index = --count;
// 4. 检查是否是最后一个到达的线程
if (index == 0) { // 最后一个线程到达,触发屏障
boolean ranAction = false;
try {
// 执行屏障动作(如果有的话)
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 开启下一代,唤醒所有等待线程
nextGeneration();
return 0; // 最后到达的线程返回0
} finally {
// 如果屏障动作执行失败,破坏屏障
if (!ranAction)
breakBarrier();
}
}
// 5. 不是最后一个线程,需要等待
for (;;) { // 无限循环等待
try {
if (!timed) // 无超时等待
trip.await();
else if (nanos > 0L) // 有超时等待
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 处理等待过程中的中断
if (g == generation && !g.broken) {
breakBarrier(); // 破坏屏障
throw ie;
} else {
// 如果是其他代或屏障已破坏,恢复中断状态
Thread.currentThread().interrupt();
}
}
// 6. 被唤醒后的检查
if (g.broken) // 屏障被破坏
throw new BrokenBarrierException();
if (g != generation) // 进入了新的代,说明屏障已触发
return index;
if (timed && nanos <= 0L) { // 超时
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock(); // 释放锁
}
}
2.3.3 Generation机制详解
nextGeneration方法:开启新一代屏障
/**
* 开启下一代屏障:实现循环使用的关键
* 这个方法由最后到达屏障的线程调用
*/
private void nextGeneration() {
// 1. 唤醒所有在当前代等待的线程
trip.signalAll();
// 2. 重置等待计数器,为下一轮使用做准备
count = parties;
// 3. 创建新的Generation对象,标志新一轮开始
generation = new Generation();
}
breakBarrier方法:破坏屏障
/**
* 破坏屏障:当发生异常或中断时调用
* 会导致所有等待线程抛出BrokenBarrierException
*/
private void breakBarrier() {
// 1. 标记当前代为已破坏
generation.broken = true;
// 2. 重置计数器
count = parties;
// 3. 唤醒所有等待线程(它们会检查到broken状态并抛出异常)
trip.signalAll();
}
2.3.4 公共方法调用链路
await()方法调用链:
// 1. 用户调用await()
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L); // 调用核心等待方法,无超时
} catch (TimeoutException toe) {
throw new Error(toe); // 不应该发生,因为没有设置超时
}
}
// 2. 带超时的await方法
public int await(long timeout, TimeUnit unit)
throws InterruptedException, BrokenBarrierException, TimeoutException {
return dowait(true, unit.toNanos(timeout)); // 调用核心等待方法,有超时
}
reset()方法:重置屏障
/**
* 重置屏障到初始状态
* 会破坏当前代,并创建新的代
*/
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // 先破坏当前代
nextGeneration(); // 然后开启新代
} finally {
lock.unlock();
}
}
2.3.5 CyclicBarrier工作流程图
flowchart TD
A[线程调用await] --> B[获取锁]
B --> C{屏障已破坏?}
C -->|是| D[抛出BrokenBarrierException]
C -->|否| E{线程被中断?}
E -->|是| F[破坏屏障]
F --> G[抛出InterruptedException]
E -->|否| H[count递减]
H --> I{count == 0?}
I -->|是| J[最后一个线程到达]
I -->|否| K[等待其他线程]
J --> L{有屏障动作?}
L -->|是| M[执行屏障动作]
L -->|否| N[开启下一代]
M --> O{执行成功?}
O -->|是| N
O -->|否| F
N --> P[唤醒所有等待线程]
P --> Q[返回索引0]
K --> R[调用condition.await]
R --> S{被唤醒}
S --> T{屏障破坏?}
T -->|是| D
T -->|否| U{代已变化?}
U -->|是| V[屏障已触发]
U -->|否| W{超时?}
W -->|是| X[破坏屏障并抛出TimeoutException]
W -->|否| R
V --> Y[返回线程索引]
style A fill:#e1f5fe
style J fill:#e8f5e8
style D fill:#ffebee
style G fill:#ffebee
style X fill:#ffebee
style Q fill:#f3e5f5
style Y fill:#f3e5f5
2.3.6 关键设计特点总结
- Generation机制:通过Generation对象区分不同轮次,实现真正的循环使用
- 递减计数:每个线程到达时count减1,最后一个线程(count=0)负责触发屏障
- 异常传播:任何线程的异常都会破坏整个屏障,确保所有线程状态一致
- 锁机制:使用ReentrantLock而非AQS,更适合复杂的状态管理
- 条件等待:使用Condition实现线程的精确等待和唤醒
3. CountDownLatch(倒计时门闩)
3.1 概念介绍
CountDownLatch是一个同步辅助类,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。CountDownLatch用给定的计数初始化,await
方法阻塞,直到由于countDown
方法的调用而使当前计数达到零。
核心特点:
- 一次性使用,计数不能重置
- 一个或多个线程等待,直到其他线程完成操作
- 支持超时等待
- 计数为0后,所有等待线程立即释放
3.2 使用示例
示例1:等待多个服务启动
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ServiceManager {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final int serviceCount;
public ServiceManager(int serviceCount) {
this.serviceCount = serviceCount;
this.startSignal = new CountDownLatch(1); // 启动信号
this.doneSignal = new CountDownLatch(serviceCount); // 完成信号
}
public void startServices() throws InterruptedException {
// 创建并启动服务线程
for (int i = 0; i < serviceCount; i++) {
final int serviceId = i;
new Thread(() -> {
try {
System.out.println("服务 " + serviceId + " 准备就绪,等待启动信号");
startSignal.await(); // 等待启动信号
System.out.println("服务 " + serviceId + " 开始启动");
Thread.sleep(2000); // 模拟启动时间
System.out.println("服务 " + serviceId + " 启动完成");
doneSignal.countDown(); // 通知完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
Thread.sleep(1000); // 准备时间
System.out.println("发送启动信号");
startSignal.countDown(); // 发送启动信号
System.out.println("等待所有服务启动完成...");
doneSignal.await(); // 等待所有服务完成
System.out.println("所有服务启动完成!");
}
}
示例2:并行任务执行
public class ParallelTaskExecutor {
public void executeParallelTasks(List<Runnable> tasks) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(tasks.size());
for (Runnable task : tasks) {
new Thread(() -> {
try {
task.run();
} finally {
latch.countDown(); // 任务完成,计数减1
}
}).start();
}
// 等待所有任务完成
if (latch.await(30, TimeUnit.SECONDS)) {
System.out.println("所有任务执行完成");
} else {
System.out.println("任务执行超时");
}
}
}
3.3 源码深度分析
3.3.1 核心架构设计
CountDownLatch基于AQS的共享模式实现,设计简洁而高效。与Semaphore不同,它是一次性使用的同步工具:
/**
* CountDownLatch的核心实现
* 基于AQS共享模式,使用state字段作为倒计时计数器
*/
public class CountDownLatch {
/**
* CountDownLatch的同步器实现
* 继承AQS,重写共享模式的获取和释放方法
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
/**
* 构造函数:初始化计数值
* @param count 初始计数,存储在AQS的state字段中
*/
Sync(int count) {
setState(count); // 将计数值设置到AQS的state字段
}
/**
* 获取当前计数值
* @return 当前state值,即剩余计数
*/
int getCount() {
return getState();
}
/**
* 尝试获取共享锁(等待操作的核心)
* 这是AQS共享模式的关键方法
* @param acquires 获取参数(对CountDownLatch无意义,通常为1)
* @return 1表示获取成功,-1表示需要等待
*/
protected int tryAcquireShared(int acquires) {
// 关键逻辑:只有当计数为0时才能获取成功
// 这实现了"等待计数归零"的语义
return (getState() == 0) ? 1 : -1;
}
/**
* 尝试释放共享锁(倒计时操作的核心)
* @param releases 释放参数(通常为1,表示计数减1)
* @return true表示释放成功且应该唤醒等待线程
*/
protected boolean tryReleaseShared(int releases) {
// 使用CAS循环确保原子性
for (;;) {
int c = getState(); // 获取当前计数
// 如果计数已经为0,无需再减(防止负数)
if (c == 0)
return false;
// 计算新的计数值
int nextc = c - 1;
// CAS更新计数值
if (compareAndSetState(c, nextc))
// 关键:只有当计数减到0时才返回true
// 返回true会触发AQS唤醒所有等待线程
return nextc == 0;
}
}
}
/** 同步器实例 */
private final Sync sync;
/**
* 构造函数
* @param count 初始计数值,必须非负
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
}
3.3.2 核心方法调用链路分析
await()方法完整调用链:
/**
* 等待计数归零的方法调用链
*/
// 1. 用户调用await()
public void await() throws InterruptedException {
// 委托给AQS的可中断共享获取方法
sync.acquireSharedInterruptibly(1);
}
// 2. AQS的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
// 检查中断状态
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取共享锁
if (tryAcquireShared(arg) < 0) // 调用CountDownLatch重写的方法
doAcquireSharedInterruptibly(arg); // 获取失败,进入等待队列
}
// 3. CountDownLatch重写的tryAcquireShared
protected int tryAcquireShared(int acquires) {
// 简单而关键的逻辑:计数为0时返回1(成功),否则返回-1(失败)
return (getState() == 0) ? 1 : -1;
}
// 4. 如果获取失败,AQS的doAcquireSharedInterruptibly方法
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 创建共享模式节点并加入等待队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) { // 自旋等待
final Node p = node.predecessor();
if (p == head) { // 前驱是头节点,尝试获取
int r = tryAcquireShared(arg);
if (r >= 0) { // 获取成功(计数已归零)
setHeadAndPropagate(node, r); // 设置新头节点并传播唤醒
p.next = null;
failed = false;
return;
}
}
// 检查是否需要阻塞,并处理中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
countDown()方法完整调用链:
/**
* 倒计时方法调用链
*/
// 1. 用户调用countDown()
public void countDown() {
// 委托给AQS的共享释放方法
sync.releaseShared(1);
}
// 2. AQS的releaseShared方法
public final boolean releaseShared(int arg) {
// 尝试释放共享锁
if (tryReleaseShared(arg)) { // 调用CountDownLatch重写的方法
doReleaseShared(); // 释放成功,唤醒等待线程
return true;
}
return false;
}
// 3. CountDownLatch重写的tryReleaseShared
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState(); // 获取当前计数
if (c == 0)
return false; // 已经为0,无需释放
int nextc = c - 1; // 计数减1
if (compareAndSetState(c, nextc))
return nextc == 0; // 只有减到0时才返回true,触发唤醒
}
}
// 4. 如果返回true,AQS的doReleaseShared方法
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 需要唤醒后继节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h); // 唤醒后继节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head) // 头节点没有变化,退出循环
break;
}
}
3.3.3 带超时的等待方法
/**
* 带超时的等待方法
* @param timeout 超时时间
* @param unit 时间单位
* @return true表示在超时前计数归零,false表示超时
*/
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
// 委托给AQS的带超时共享获取方法
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// AQS的tryAcquireSharedNanos方法
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 先尝试快速获取,失败则进入带超时的等待
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
3.3.4 CountDownLatch工作流程图
flowchart TD
A["Thread calls await()"] --> B{"Check interrupt status"}
B -->|"Interrupted"| C["Throw InterruptedException"]
B -->|"Not interrupted"| D["Call tryAcquireShared()"]
D --> E{"Count equals 0?"}
E -->|"Yes"| F["Acquire success, method returns"]
E -->|"No"| G["Add to wait queue"]
G --> H["Spin wait or block"]
H --> I{"Woken up?"}
I -->|"Yes"| D
I -->|"No"| J{"Check interrupt?"}
J -->|"Yes"| C
J -->|"No"| H
K["Thread calls countDown()"] --> L["Call tryReleaseShared()"]
L --> M{"Current count is 0?"}
M -->|"Yes"| N["No operation, return false"]
M -->|"No"| O["CAS decrement count"]
O --> P{"CAS success?"}
P -->|"No"| O
P -->|"Yes"| Q{"Count decremented to 0?"}
Q -->|"No"| R["Return false, no wake up"]
Q -->|"Yes"| S["Return true, trigger wake up"]
S --> T["Call doReleaseShared()"]
T --> U["Wake up all waiting threads"]
U --> V["countDown() returns"]
style A fill:#e1f5fe
style K fill:#e8f5e8
style C fill:#ffebee
style F fill:#f3e5f5
style V fill:#f3e5f5
style S fill:#fff3e0
3.3.5 关键特性分析
1. 一次性使用特性:
// CountDownLatch的计数只能递减,不能重置
// 一旦计数归零,所有后续的await()调用都会立即返回
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
// 如果计数已经为0,tryAcquireShared会立即返回1
// 不会进入等待队列
}
2. 共享模式的传播特性:
// 当计数归零时,AQS会唤醒所有等待线程
// 这是通过共享模式的传播机制实现的
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// propagate > 0 表示还有资源可用,继续传播唤醒
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); // 继续唤醒后续节点
}
}
3. 线程安全保证:
// 使用CAS操作保证计数递减的原子性
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) return false;
int nextc = c - 1;
// CAS确保在并发环境下的正确性
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
3.3.6 与其他同步工具的对比
特性 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
AQS模式 | 共享模式 | 不使用AQS | 共享模式 |
计数方向 | 只能递减 | 固定循环 | 可增可减 |
重用性 | 一次性 | 可循环 | 可重复 |
等待语义 | 等待计数归零 | 等待所有线程到达 | 等待许可证 |
唤醒机制 | 一次性唤醒所有 | 循环唤醒所有 | 按需唤醒 |
4. 三者对比总结
特性 | Semaphore | CyclicBarrier | CountDownLatch |
---|---|---|---|
用途 | 控制资源访问数量 | 多线程同步到达屏障点 | 等待多个操作完成 |
重用性 | 可重复使用 | 可循环使用 | 一次性使用 |
等待方式 | 获取许可证 | 所有线程互相等待 | 一个/多个线程等待 |
触发条件 | 有可用许可证 | 所有线程到达屏障 | 计数归零 |
计数方向 | 可增可减 | 固定数量循环 | 只能递减 |
异常处理 | 支持中断 | 支持中断和超时 | 支持中断和超时 |
典型场景 | 限流、资源池 | 分阶段并行计算 | 服务启动、任务完成等待 |
5. 最佳实践
5.1 选择原则
- Semaphore:当需要控制同时访问某个资源的线程数量时使用
- CyclicBarrier:当多个线程需要在某个点同步,并且需要重复这个过程时使用
- CountDownLatch:当需要等待多个操作完成后再继续执行时使用
5.2 注意事项
- 资源释放:使用Semaphore时要确保在finally块中释放许可证
- 异常处理:CyclicBarrier和CountDownLatch都要妥善处理中断异常
- 超时设置:在生产环境中建议使用带超时的等待方法
- 线程安全:这些类本身是线程安全的,但使用它们的业务逻辑需要考虑线程安全
5.3 性能考虑
- Semaphore支持公平和非公平模式,非公平模式性能更好但可能导致线程饥饿
- CyclicBarrier基于ReentrantLock实现,在高并发场景下性能可能不如基于CAS的实现
- CountDownLatch基于AQS的共享模式,性能较好且适合一次性场景
通过深入理解这三个并发控制工具的原理和使用场景,我们可以在实际开发中选择最合适的工具来解决并发协调问题,编写出更加健壮和高效的多线程程序。