钩子方法是 AQS 留给子类定义同步语义 的扩展点。它们全部声明为 protected,由模板方法在特定时机回调。以下对五个核心钩子方法逐一进行源码级剖析。
一、独占模式钩子
1. tryAcquire(int arg)
| 属性 | 说明 |
|---|---|
| 调用者 | acquire / acquireInterruptibly / tryAcquireNanos |
| 参数 | arg:期望获取的资源数量(通常为 1) |
| 返回值 | true 表示获取成功;false 表示获取失败 |
| 副作用 | 成功时通常需要修改 state 并设置独占线程 |
实现要点:
- 必须非阻塞 ,通常用
if-else判断并立即返回。 - 修改
state时必须使用compareAndSetState(除重入场景外)。 - 需正确处理重入:若当前线程已独占资源,则增加计数。
📝 ReentrantLock 非公平锁实现:
java
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 无锁:CAS 抢占
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入:计数累加(无需 CAS,单线程安全)
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2. tryRelease(int arg)
| 属性 | 说明 |
|---|---|
| 调用者 | release |
| 参数 | arg:期望释放的资源数量(通常为 1) |
| 返回值 | true 表示资源已完全释放 (state == 0);false 表示仍持有资源 |
| 副作用 | 减少 state,完全释放时清除独占线程 |
实现要点:
- 该方法由持有锁的线程单线程调用 ,因此不需要 CAS ,直接用
setState。 - 需先检查调用者是否为持有者,否则抛出
IllegalMonitorStateException。 - 仅当
state减至 0 时返回true,触发 AQS 唤醒后继节点。
📝 ReentrantLock.Sync 实现:
java
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
二、共享模式钩子
3. tryAcquireShared(int arg)
| 属性 | 说明 |
|---|---|
| 调用者 | acquireShared / acquireSharedInterruptibly / tryAcquireSharedNanos |
| 参数 | arg:期望获取的资源数量 |
| 返回值 | 负值 :获取失败;0 或正值 :获取成功,数值表示获取后仍剩余的资源数 |
| 副作用 | 成功时 CAS 扣减 state |
实现要点:
- 必须使用 CAS 循环 扣减
state。 - 返回值直接影响 AQS 是否进行链式唤醒 :若
> 0,AQS 会继续唤醒后续共享节点。
📝 Semaphore 非公平实现:
java
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
4. tryReleaseShared(int arg)
| 属性 | 说明 |
|---|---|
| 调用者 | releaseShared |
| 参数 | arg:期望释放的资源数量 |
| 返回值 | true 表示释放成功且状态已变更(可能从 0 变为正数),需要唤醒等待者 |
| 副作用 | CAS 增加 state |
实现要点:
- 必须使用 CAS 循环 增加
state,因为释放操作可能由多个线程并发执行。 - 返回
true时,AQS 会执行doReleaseShared()唤醒等待线程。
📝 Semaphore 实现:
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;
}
}
三、查询钩子
5. isHeldExclusively()
| 属性 | 说明 |
|---|---|
| 调用者 | AQS 内部条件队列(ConditionObject)及诊断方法(如 hasQueuedThreads) |
| 返回值 | true 表示当前线程独占持有同步器 |
| 副作用 | 仅查询,不修改状态 |
实现要点:
- 通常通过比较
getExclusiveOwnerThread()与Thread.currentThread()实现。 - 用于
Condition.await()前置检查:若未持有锁却调用await,则抛出IllegalMonitorStateException。
📝 ReentrantLock.Sync 实现:
java
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
四、钩子方法决策矩阵
| 钩子方法 | 模式 | 调用时机 | 核心问题 | 修改 state |
是否 CAS |
|---|---|---|---|---|---|
tryAcquire |
独占 | 尝试获取锁时 | 现在能独占吗? | 0 → 1 / 累加重入 | 是(重入除外) |
tryRelease |
独占 | 释放锁时 | 完全释放了吗? | 递减,归零时清空 | 否(单线程) |
tryAcquireShared |
共享 | 尝试获取共享资源时 | 能获取吗?剩多少? | 扣减 | 是 |
tryReleaseShared |
共享 | 释放共享资源时 | 资源增加了吗? | 增加 | 是 |
isHeldExclusively |
独占 | 条件检查时 | 当前线程是持有者吗? | 只读 | --- |
五、钩子方法与模板方法的交互时序
以共享模式获取为例,展示 tryAcquireShared 如何嵌入模板流程:
sequenceDiagram
participant T as 线程
participant TM as acquireShared(模板)
participant HK as tryAcquireShared(钩子)
participant Q as CLH队列
T->>TM: acquireShared(1)
TM->>HK: 1. tryAcquireShared(1)
HK-->>TM: 返回 remaining
alt remaining >= 0
TM-->>T: 直接成功
else remaining < 0
TM->>Q: 2. addWaiter(SHARED)
loop 自旋
Q->>HK: 前驱为head时再次 tryAcquireShared
HK-->>Q: remaining
alt remaining >= 0
Q->>Q: setHeadAndPropagate (若remaining>0则继续唤醒)
Q-->>T: 成功返回
else
Q->>Q: park阻塞
end
end
end
六、设计钩子方法的目的再总结
- 分离变化与不变:将不可变的排队、阻塞、唤醒算法固定于模板方法,将可变的资源判定逻辑开放给钩子。
- 强制无锁化语义 :钩子方法不声明
synchronized,强制子类使用 CAS,保证整个 AQS 框架的无锁高性能特性。 - 最小化实现成本:开发者仅需 5~20 行代码实现钩子,即可获得一个功能完整的同步器。
- 防御性保护 :钩子为
protected,禁止外部直接调用,确保所有访问都经过模板方法的排队策略,防止插队破坏语义。