说句大实话。 AQS 被写成一整套框架,是给 JVM 和 JDK 用的;
但在面试里,它被压缩成了三道判断题。
你能不能把这三段说清楚,
直接决定了面试官心里对你的定位:
“用过” vs “懂过”。
第一段:state —— 并发世界里最冷静的数字
很多人讲 AQS,会从队列开始。
但在我这儿,那是第二层。
真正的起点,是这个看起来毫不起眼的字段:
public abstract class AbstractQueuedSynchronizer {
private volatile int state;
protected final int getState() {
return state;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
面试时我通常会停在这儿,看你反应。
如果你开始讲“volatile 保证可见性”,
我会点头,但不会记分。
如果你接着说一句:
AQS 里没有“锁对象”,
谁改 state 成功,谁就暂时拥有秩序
那我基本知道,你翻过源码。
AQS 的所有公平、不公平、独占、共享,
最后都要落回到这个 state 上。
它不代表“线程”,只代表“资格”。
第二段:acquire —— 不成功,就别硬撑
第二段,是我最爱问的。
“acquire 到底做了什么?”
大多数人会说:
“先 tryAcquire,失败就阻塞。”
这话没错,但不够真实。
真实的 AQS acquire,长这样(简化版):
public final void acquire(int arg) {
if (!tryAcquire(arg)) {
Node node = addWaiter();
acquireQueued(node, arg);
}
}
这段代码的气质非常 AQS。
它没有 retry,没有 while 自旋到死,
只给你一次机会:
- 成了,你走
- 不成,进队
这一步非常关键,因为它直接回答了一个面试官心里一直在想的问题:
AQS 是“先排队再抢”,还是“先抢再排队”?
答案是:
先抢一次,失败才排队。
这也是为什么很多 AQS 锁默认不公平。
新线程是有“插队机会”的。
第三段:release —— 走之前,必须叫人
很多候选人写 AQS 子类的时候,
会特别认真地写 tryAcquire,
然后在 tryRelease 上随手一写。
这是个非常危险的信号。
因为 AQS 的释放逻辑,真正重要的不是“释放”,
而是**“唤醒谁”**。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0) {
unparkSuccessor(h);
}
return true;
}
return false;
}
这段代码我面试时经常原样贴出来。
然后问一句很简单的问题:
“如果你
tryRelease返回 true,但忘了唤醒,会怎样?”
真正懂 AQS 的人,通常会停顿一下,说:
后面的线程会永远睡着
这不是夸张,这是事实。
AQS 不会替你兜底。
你释放了资格,却没通知下一个人,
秩序就断在这儿。
把这三段连起来,AQS 就完整了
你会发现,其实整个 AQS 就是一条非常冷静的流水线:
- state 决定资格
- acquire 决定去留
- release 决定交接
队列只是载体,
公平与否只是策略,
共享和独占只是 state 的数学含义不同。
但这三段,是骨头。
面试里的“分水岭时刻”
如果你在面试中能自然地说出这些话:
- “AQS 是先 CAS 抢一次,再决定排不排队”
- “state 不是锁,是资格位”
- “release 的重点不是 setState,是唤醒”
通常,面试官就不会再追源码细节了。
因为他已经知道一件事:
你不是只看过总结。