AQS在锁实现中的应用详解
一、ReentrantLock 实现原理
1. 核心设计
- 独占锁:基于AQS独占模式实现
- 可重入:支持同一线程多次获取锁
- 公平/非公平模式 :通过内部类
FairSync和NonfairSync实现
2. AQS状态映射
state含义:锁的重入计数0:锁未被占用>0:锁被占用的次数(重入次数)
3. 核心实现代码
非公平锁获取(NonfairSync.tryAcquire):
java
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 1. 锁未被占用,直接CAS获取
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 2. 锁已被当前线程占用,重入计数+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // 溢出检查
throw new Error("Maximum lock count exceeded");
setState(nextc); // 已持有锁,无需CAS
return true;
}
return false; // 锁被其他线程占用
}
公平锁获取(FairSync.tryAcquire):
java
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平性体现:检查CLH队列是否有等待的线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入逻辑与非公平锁相同
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
锁释放(Sync.tryRelease):
java
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 重入计数为0,释放锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
二、CountDownLatch 实现原理
1. 核心设计
- 共享锁:基于AQS共享模式实现
- 一次性:计数器减为0后不可重置
- 多线程等待:多个线程可等待同一个计数器
2. AQS状态映射
state含义:计数器值- 初始化为构造函数传入的计数
- 调用
countDown()时,state递减 - 当state=0时,所有等待线程被唤醒
3. 核心实现代码
共享锁获取(tryAcquireShared):
java
protected int tryAcquireShared(int acquires) {
// 计数器为0时,获取锁成功(返回1);否则失败(返回-1)
return (getState() == 0) ? 1 : -1;
}
共享锁释放(tryReleaseShared):
java
protected boolean tryReleaseShared(int releases) {
// 循环CAS递减计数器,直到为0
for (;;) {
int c = getState();
if (c == 0)
return false; // 计数器已为0,无需处理
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0; // 计数器减为0时,返回true,唤醒所有等待线程
}
}
三、Semaphore 实现原理
1. 核心设计
- 共享锁:基于AQS共享模式实现
- 许可机制 :通过
state控制可用资源数 - 公平/非公平模式:与ReentrantLock类似
2. AQS状态映射
state含义:可用许可数- 初始化为构造函数传入的许可数
- 调用
acquire()时,state递减(获取许可) - 调用
release()时,state递增(释放许可)
3. 核心实现代码
非公平获取许可(NonfairSync.tryAcquireShared):
java
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
// 许可不足或CAS失败时,返回负数(获取失败)
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平获取许可(FairSync.tryAcquireShared):
java
protected int tryAcquireShared(int acquires) {
for (;;) {
// 公平性体现:检查CLH队列是否有等待的线程
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
释放许可(Sync.tryReleaseShared):
java
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; // 释放成功,唤醒后续等待线程
}
}
四、CountDownLatch 与 CyclicBarrier 的区别(面试重点)
| 对比维度 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 底层实现 | 基于AQS共享模式 | 基于ReentrantLock + Condition |
| 是否可重置 | 不可重置(一次性) | 可重置(通过reset()方法) |
| 状态管理 | 递减计数器(state从N→0) | 递增等待线程数(直到达到parties) |
| 使用场景 | 一个/多个线程等待其他线程完成 | 多个线程相互等待,达到屏障后继续执行 |
| 等待线程数 | 等待的线程数不固定(可多可少) | 固定数量的线程必须全部到达屏障 |
| 唤醒机制 | 计数器为0时,自动唤醒所有等待线程 | 最后一个线程到达时,唤醒所有等待线程 |
| 异常处理 | 无特殊异常处理 | 支持BrokenBarrierException,表示屏障被破坏 |
| 扩展功能 | 不支持回调 | 支持barrierAction,最后一个线程到达时执行 |
典型使用场景示例:
-
CountDownLatch :主线程等待多个子线程完成初始化后再执行
javaCountDownLatch latch = new CountDownLatch(3); // 3个子线程执行完成后调用countDown() // 主线程调用latch.await()等待 -
CyclicBarrier :多个线程执行到某一阶段后,相互等待,然后同时继续执行
javaCyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障!")); // 每个线程执行到barrier.await()时等待 // 3个线程都到达后,执行barrierAction,然后继续执行
五、AQS同步器对比总结
| 同步器 | 模式 | state含义 | 核心特性 | 典型场景 |
|---|---|---|---|---|
| ReentrantLock | 独占 | 重入计数 | 可重入、公平/非公平 | 替代synchronized,支持更灵活的锁控制 |
| CountDownLatch | 共享 | 计数器值 | 一次性、多线程等待 | 主线程等待子线程初始化完成 |
| Semaphore | 共享 | 可用许可数 | 可多次获取/释放、公平/非公平 | 限流、资源池控制 |
面试题:CountDownLatch与CyclicBarrier的区别?
回答模板 :
CountDownLatch和CyclicBarrier都是Java并发包中用于线程同步的工具类,但它们在底层实现、功能特性和使用场景上有明显区别:
-
底层实现不同:
- CountDownLatch基于AQS共享模式实现,通过
state变量维护计数器 - CyclicBarrier基于ReentrantLock和Condition实现,内部维护等待线程数
- CountDownLatch基于AQS共享模式实现,通过
-
是否可重置:
- CountDownLatch是一次性的,计数器减为0后不可重置
- CyclicBarrier是可重置 的,通过
reset()方法可以重新使用
-
同步机制:
- CountDownLatch:一个/多个线程等待其他线程完成,计数器从N→0
- CyclicBarrier:多个线程相互等待,直到所有线程到达屏障点
-
等待线程数:
- CountDownLatch:等待的线程数不固定,可多可少
- CyclicBarrier:必须有固定数量的线程到达屏障
-
扩展功能:
- CountDownLatch:无回调功能
- CyclicBarrier:支持
barrierAction,最后一个线程到达时执行回调
-
使用场景:
- CountDownLatch:适合主从协作场景,如主线程等待子线程完成初始化
- CyclicBarrier:适合多线程协作场景,如多个线程分阶段执行任务,每个阶段等待所有线程完成
示例代码对比:
java
// CountDownLatch示例:主线程等待3个子线程完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 模拟任务执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 子线程完成,计数器减1
}
}).start();
}
latch.await(); // 主线程等待计数器为0
// CyclicBarrier示例:3个线程相互等待,到达屏障后继续执行
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障!"));
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 第一阶段任务
Thread.sleep(1000);
barrier.await(); // 等待其他线程到达屏障
// 第二阶段任务
Thread.sleep(1000);
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
总结
AQS作为Java并发包的核心框架,通过模板方法模式为各种同步器提供了统一的实现基础。ReentrantLock、CountDownLatch、Semaphore等同步器通过重写AQS的tryAcquire/tryRelease等方法,实现了各自独特的同步语义。
CountDownLatch和CyclicBarrier作为常用的线程同步工具,虽然都用于线程等待,但在实现原理、功能特性和使用场景上有本质区别。理解它们的区别,对于正确选择和使用同步工具至关重要,也是面试中的高频考点。