面试知识库
极高 困难

AQS原理#

一句话答案#

AQS 是 Java 并发包的骨架,核心:volatile int state(资源状态)+ CLH 队列(等待线程),ReentrantLock/Semaphore/CountDownLatch 都基于它。

核心要点

AQS 是 Java 并发包的骨架,ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 都基于它实现;CyclicBarrier 是间接依赖(内部用 ReentrantLock + Condition)。

核心设计:

AQS 的两个核心结构:
1. volatile int state  → 共享资源的状态(锁计数/许可数等)
2. CLH 队列(双向链表)→ 等待获取资源的线程队列
plaintext

CLH 队列(FIFO 虚拟队列):

Head(哑节点)← → Node(thread1, CANCELLED) ← → Node(thread2, SIGNAL) ← → Tail
                                                        ↑
                                                  等待被唤醒的线程
plaintext

每个 Node 的状态(waitStatus):

  • SIGNAL(-1):后继节点需要被唤醒
  • CANCELLED(1):线程已取消等待
  • CONDITION(-2):在条件队列中等待
  • PROPAGATE(-3):共享模式下传播唤醒

以上 waitStatus 常量和下文的 acquireQueued 是 JDK 8~13 的源码。JDK 14 起 AQS 内部重写(JDK-8229442):Node 改用 status 字段(WAITING=1 / CANCELLED=负数 / COND=2),去掉了 PROPAGATE,acquireQueued 合并进统一的 acquire(node, arg, shared, interruptible, timed, time)。state + CLH 变体队列 + park/unpark 的整体思路不变。

独占锁的获取流程(以 ReentrantLock 为例):

1. tryAcquire():CAS 尝试将 state 从 0 改为 1
   ├─ 成功 → 获锁,直接返回
   └─ 失败 → 当前线程封装为 Node 加入 CLH 队列尾部

2. 进入 acquireQueued() 自旋:
   ├─ 前驱节点是 Head?→ 再次 tryAcquire()(此时有机会获锁)
   │   ├─ 成功 → 出队,成为新的 Head,返回
   │   └─ 失败 → 将前驱 waitStatus 设为 SIGNAL,然后 park() 挂起
   └─ 等待前驱节点 unpark() 唤醒 → 重复自旋
   
3. 释放锁(unlock):
   tryRelease() 将 state 减 1(重入几次就要释放几次),减到 0 才算真正释放
   unpark(head.next.thread) 唤醒队列中第一个等待线程(next 为空或已取消时从 tail 往前找)
plaintext

共享模式(Semaphore / CountDownLatch):

  • Semaphore:state 是剩余许可数,多个线程可同时持有;减到 0 后再 acquire 的线程排队等待
  • CountDownLatch 方向相反:state > 0 时 await 的线程排队,countDown 减到 0 时一次性唤醒所有等待线程
  • acquireShared() / releaseShared()

面试回答(2分钟版)

AQS 全称 AbstractQueuedSynchronizer,是 Java 并发包的核心骨架,ReentrantLock、Semaphore、CountDownLatch 等都基于它实现。AQS 内部维护两个关键结构:一个 volatile int state 表示资源状态,一个 CLH 双向链表队列存放等待获取资源的线程节点。以 ReentrantLock 为例,线程调用 lock 时先通过 CAS 尝试将 state 从 0 改为 1,成功则直接获锁;失败则把当前线程封装成 Node 加入 CLH 队列尾部,然后检查前驱节点是否为 Head,是的话再尝试一次获锁,仍然失败就将前驱 waitStatus 设为 SIGNAL 并调用 LockSupport.park() 挂起自己。释放锁时 state 减 1,减到 0 才真正释放,然后 unpark 唤醒队列中下一个等待线程。公平锁和非公平锁的区别在于:非公平锁上来直接 CAS 抢,失败才排队;公平锁先检查队列中是否有等待线程,有则老老实实排队。state 在不同实现中含义不同,ReentrantLock 中是重入计数,Semaphore 中是许可数。

追问与易错

追问方向:

  • “公平锁和非公平锁在 AQS 中怎么实现的?”→ 公平锁先看队列是否有等待,非公平直接 CAS 抢
  • “AQS 的 state 在不同实现中表示什么?”→ ReentrantLock 是重入次数,Semaphore 是剩余许可数,CountDownLatch 是剩余计数,ReentrantReadWriteLock 高 16 位是读锁计数、低 16 位是写锁重入数
  • “独占模式和共享模式的区别?”→ 独占 acquire/release,共享 acquireShared/releaseShared

易错点:

  • ❌ “AQS 用的是 CAS 自旋”——入队后是 park 阻塞,不是一直自旋
  • ❌ 混淆 CLH 队列和 Condition 队列——CLH 是同步队列,Condition 是等待队列