AQS和Lock的关系(组合模式)
大约 2 分钟
AQS(AbstractQueuedSynchronizer)和 Lock 的关系可以概括为:AQS 是底层框架,Lock 是上层接口;AQS 为 Lock 的实现提供了核心能力。
打个比方:Lock 是汽车的方向盘和踏板(用户操作接口),AQS 是汽车的发动机和传动系统(核心动力实现)。
1. 角色定位差异
Lock (接口):
- 面向使用者。
- 它定义了锁的行为规范:比如
lock()(加锁)、unlock()(解锁)、tryLock()(尝试加锁)。 - 开发者在业务代码中直接使用的是
Lock接口。
AQS (抽象类):
- 面向锁的实现者。
- 它提供了一套通用的线程同步框架:帮你管理“状态”(state)、维护“等待队列”(CLH 队列)、处理“线程阻塞与唤醒”(LockSupport)。
- 开发者通常不会直接用 AQS,而是通过继承它来构建自己的锁(如
ReentrantLock)。
2. 具体协作方式:组合模式
Java 中大多数 Lock 的实现(如 ReentrantLock, ReentrantReadWriteLock)并没有直接继承 AQS,而是采用了组合模式。它们内部通常会定义一个静态内部类(通常叫 Sync)去继承 AQS。
代码结构示例(ReentrantLock)
public class ReentrantLock implements Lock {
// 1. 内部持有 AQS 的子类对象
private final Sync sync;
// 2. 定义 Sync 继承 AQS,实现具体的同步逻辑
abstract static class Sync extends AbstractQueuedSynchronizer {
// 重写 tryAcquire 等方法
}
// 3. Lock 接口的方法,统统委托给 sync 去干活
public void lock() {
sync.lock(); // 委托给 AQS
}
public void unlock() {
sync.release(1); // 委托给 AQS
}
}3. AQS 帮 Lock 干了什么脏活累活?
如果你要自己实现一个 Lock,如果没有 AQS,你需要自己处理以下难题:
- 状态管理:锁是被谁拿了?拿了几次?(AQS 提供了
state变量和原子操作) - 排队机制:如果锁被占用了,新来的线程去哪?(AQS 提供了 CLH 队列管理入队出队)
- 阻塞唤醒:怎么让排队的线程挂起?怎么在释放锁时唤醒它们?(AQS 封装了
LockSupport的 park/unpark 逻辑)
有了 AQS,实现一个 Lock 只需要关注最核心的业务逻辑(即:什么情况下算获取锁成功?):
- 实现
tryAcquire(int):决定能不能抢到锁。 - 实现
tryRelease(int):决定能不能释放锁。
总结
- Lock 定义了“是什么”(接口)。
- AQS 解决了“怎么做”(实现)。
- 大多数 JDK 锁(ReentrantLock, ReadWriteLock)和同步工具(Semaphore, CountDownLatch)本质上都是 AQS 的不同皮肤,里子都是同一个队列同步器。