真实面经题目 · 原创解析
AQS 的核心原理是什么?
AQS 是 Java 并发包中很多同步器的基础框架,它用一个 volatile 的 state 表示同步状态,用 CAS 保证状态修改的原子性,并通过一个变体 CLH 双向等待队列管理竞争失败的线程。面试回答时要把它讲成“状态管理 + 队列排队 + 阻塞唤醒 + 模板方法扩展”的组合,而不是只背 ReentrantLock 底层用了 AQS。
真实面经题目 · 原创解析
AQS 是 Java 并发包中很多同步器的基础框架,它用一个 volatile 的 state 表示同步状态,用 CAS 保证状态修改的原子性,并通过一个变体 CLH 双向等待队列管理竞争失败的线程。面试回答时要把它讲成“状态管理 + 队列排队 + 阻塞唤醒 + 模板方法扩展”的组合,而不是只背 ReentrantLock 底层用了 AQS。
AQS 的核心原理可以概括为:用一个 volatile int state 表示同步资源状态,用 CAS 去尝试改变 state,竞争失败的线程会被封装成 Node 加入一个类似 CLH 的 FIFO 等待队列,然后通过 LockSupport.park 挂起,等前驱节点释放资源后再通过 unpark 唤醒后继节点继续竞争。AQS 本身不直接定义“怎样才算获取成功”,而是提供 acquire、release、acquireShared、releaseShared 等通用流程,把 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared 留给子类实现,所以它是模板方法模式的典型应用。ReentrantLock 基于独占模式实现互斥锁,Semaphore 和 CountDownLatch 基于共享模式实现多线程共享许可或等待计数归零。公平锁会更严格检查队列中是否已有前驱节点,非公平锁通常允许新线程先 CAS 抢占 state,因此吞吐量更高但可能增加等待不确定性。AQS 还支持可中断获取和超时获取,本质是在排队等待过程中响应中断或按剩余时间控制 park 的时长。
AQS 不是某一个具体锁,而是构建锁和同步器的基础框架。它把并发控制拆成两部分:一部分是通用的排队、阻塞、唤醒和取消逻辑,另一部分是具体同步器自己定义的资源获取规则。这样 ReentrantLock、Semaphore、CountDownLatch 可以复用同一套等待队列和线程调度机制,只在 state 的含义和获取释放条件上不同。
AQS 的同步状态由 volatile int state 承载,volatile 保证线程之间能看到最新状态,CAS 保证修改状态时具备原子性。不同同步器会赋予 state 不同语义:在 ReentrantLock 中它通常表示重入次数,在 Semaphore 中表示剩余许可数量,在 CountDownLatch 中表示尚未完成的计数。因此理解 AQS 时不能把 state 固定理解成锁标志,它更像一个可被子类解释的同步资源计数器。
独占模式表示同一时刻只有一个线程能成功获取同步状态,典型代表是 ReentrantLock。线程调用 acquire 时会先尝试 tryAcquire,如果成功就成为持有者;失败则进入等待队列并在合适时机挂起。释放时通过 tryRelease 修改 state,完全释放后再唤醒后继节点。重入锁的关键在于同一持有线程再次获取时增加 state,释放时逐次减少 state,直到归零才真正让出锁。
共享模式允许多个线程在条件满足时同时通过,典型代表是 Semaphore 和 CountDownLatch。共享获取会调用 tryAcquireShared,返回值用于表达是否成功以及是否还能继续传播唤醒。Semaphore 的 state 表示许可数,获取许可会减少 state,释放许可会增加 state;CountDownLatch 的 state 表示计数,await 线程在 state 不为零时等待,countDown 把 state 减到零后会唤醒所有等待线程。
竞争失败的线程不会忙等消耗 CPU,而是被封装成节点加入 AQS 维护的 FIFO 双向等待队列。这个队列借鉴 CLH 锁思想,线程主要关注自己的前驱节点状态,当前驱释放或取消后再获得竞争机会。AQS 通过 head、tail 维护队列边界,通过节点状态记录取消、等待唤醒、条件队列等信息。队列化的意义是把无序抢占变成有序等待,减少竞争风暴。
AQS 使用 LockSupport.park 和 unpark 完成线程挂起与唤醒。线程入队后不会立刻无条件 park,而是先判断前驱节点是否稳定、自己是否应该阻塞,避免错过释放信号。释放同步状态时,AQS 会寻找有效的后继节点并 unpark,让它从 park 返回后重新尝试获取资源。park 返回不等于获取成功,因为可能被中断、虚假唤醒或被其他线程抢先成功,所以返回后必须重新走竞争判断。
AQS 的设计重点是模板方法:框架层实现 acquire、release、入队、取消、park、unpark 等稳定流程,子类只实现 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared 等钩子方法。这种设计让同步器作者不必反复处理复杂的线程排队细节,只需要准确描述 state 如何变化、什么条件下获取成功、什么条件下释放完成。面试中说清这一点,比机械背源码路径更有价值。
公平和非公平主要差在获取资源时是否尊重等待队列顺序。公平锁在尝试获取前通常会检查队列中是否已有前驱节点,有前驱就不插队;非公平锁则可能在刚进入时直接 CAS 抢 state,即使队列里已有等待线程也可能抢到。非公平策略减少线程切换和排队成本,吞吐量通常更好;公平策略等待顺序更稳定,但在高并发下可能牺牲性能。
volatile 保证 state 的可见性,CAS 保证对 state 修改的原子性,两者配合可以在用户态完成快速竞争。AQS 不是完全不用阻塞,而是先让线程尝试无阻塞获取,失败后再进入队列并 park,这样在低竞争场景下性能更好,在高竞争场景下又能有序等待。
ReentrantLock 的 state 主要表示独占锁的重入次数,state 为零表示未被持有,持有线程重入会递增,释放会递减。Semaphore 的 state 表示剩余许可数,获取许可时减少,释放许可时增加,只要许可足够,多个线程就可以同时通过。
经典 CLH 锁强调线程基于前驱节点状态自旋等待,AQS 借鉴了这种前驱驱动的排队思想,但改造成 JVM 中更实用的双向 FIFO 队列。AQS 节点不仅记录前驱后继,还记录等待状态,并通过 park/unpark 阻塞和唤醒线程,避免长期自旋浪费 CPU。
主要差在 tryAcquire 的策略。公平锁通常会先判断当前队列中是否存在排在自己前面的等待线程,如果存在就不抢占;非公平锁则可能先直接 CAS 修改 state,成功就获得锁。前者等待顺序更可控,后者吞吐量通常更高。
可中断获取会在等待过程中检查中断状态,如果线程被中断,就从队列中取消并抛出中断异常。超时获取会计算剩余等待时间,并用带时间参数的 park 控制挂起时长,时间耗尽后同样取消排队并返回失败。