hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶
面试官:详细说说AQS
AQS(AbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent.locks
)的核心基础框架,用于构建锁和其他同步器(如 ReentrantLock
、Semaphore
、CountDownLatch
等)。它通过模板方法模式定义了一套多线程访问共享资源的同步机制,开发者只需继承 AQS 并实现特定方法,即可自定义同步器。
AQS 的核心设计思想
AQS 的核心是 “同步状态管理” + “线程排队机制”:
-
同步状态(
state
字段):
通过一个volatile int state
字段表示共享资源的状态(如锁是否被占用、信号量剩余许可数等)。- 开发者需根据同步器的语义定义
state
的用途(例如:ReentrantLock
用state
表示重入次数)。
- 开发者需根据同步器的语义定义
-
CLH 队列(线程等待队列):
采用 CLH 变体的双向链表队列,将未获取到资源的线程封装为Node
节点并排队等待。- CLH 队列的每个节点保存了前驱和后继节点的引用,以及线程的状态(如是否被取消)。
-
模板方法模式:
AQS 提供通用的排队和阻塞机制,开发者只需实现以下关键方法(需保证线程安全):tryAcquire(int arg)
:尝试以独占模式获取资源。tryRelease(int arg)
:尝试释放独占模式的资源。tryAcquireShared(int arg)
:尝试以共享模式获取资源。tryReleaseShared(int arg)
:尝试释放共享模式的资源。isHeldExclusively()
:判断当前线程是否独占资源。
AQS 的内部结构
-
state
字段private volatile int state; // 核心状态变量
- 通过
getState()
,setState()
,compareAndSetState()
方法原子操作状态。
- 通过
-
CLH 队列
// 队列头尾指针 private transient volatile Node head; private transient volatile Node tail;// Node 节点结构(每个等待线程封装为一个 Node) static final class Node {volatile int waitStatus; // 等待状态(如 CANCELLED、SIGNAL)volatile Node prev; // 前驱节点volatile Node next; // 后继节点volatile Thread thread; // 关联的线程Node nextWaiter; // 条件队列的下一节点(用于 Condition) }
-
两种模式
- 独占模式(Exclusive):资源一次只能被一个线程占用(如
ReentrantLock
)。 - 共享模式(Shared):资源可被多个线程共享(如
Semaphore
、CountDownLatch
)。
- 独占模式(Exclusive):资源一次只能被一个线程占用(如
AQS 的工作流程
1. 获取资源(以独占模式为例)
-
步骤:
- 调用
acquire(int arg)
方法。 - 先尝试
tryAcquire(arg)
(需开发者实现),若成功则直接返回。 - 若失败,将线程封装为
Node
加入 CLH 队列尾部,并通过acquireQueued()
自旋或阻塞等待。 - 在队列中等待的线程会不断检查前驱节点是否为头节点,若是则再次尝试获取资源。
- 调用
-
关键代码逻辑:
public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); // 恢复中断状态 }
2. 释放资源
-
步骤:
- 调用
release(int arg)
方法。 - 先尝试
tryRelease(arg)
(需开发者实现),若成功则唤醒队列中的后继节点。
- 调用
-
关键代码逻辑:
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 唤醒后继节点的线程return true;}return false; }
3. 中断与超时
- AQS 支持可中断的获取资源(
acquireInterruptibly()
)和超时获取(tryAcquireNanos()
)。 - 若线程在等待中被中断或超时,会移除对应的节点并抛出
InterruptedException
。
AQS 的应用场景
-
ReentrantLock
- 通过实现
tryAcquire
和tryRelease
方法,管理锁的重入计数(state
字段记录持有锁的线程和重入次数)。
- 通过实现
-
Semaphore
- 使用
state
表示剩余许可数,tryAcquireShared
尝试获取许可,tryReleaseShared
释放许可。
- 使用
-
CountDownLatch
state
初始化为计数器值,await()
调用acquireShared
,countDown()
调用releaseShared
。
-
ReentrantReadWriteLock
- 将
state
的高 16 位用于读锁(共享模式),低 16 位用于写锁(独占模式)。
- 将
AQS 的设计优势
-
灵活性
开发者只需关注资源状态的管理(state
操作),无需处理线程排队、阻塞与唤醒等底层逻辑。 -
高效性
- CLH 队列的变体设计减少了锁竞争,通过自旋和 CAS 操作提升性能。
- 双向链表便于处理取消和超时的节点。
-
可扩展性
支持独占和共享模式,且可通过Condition
实现更复杂的线程通信(如生产者-消费者模型)。
AQS 的关键源码技巧
-
自旋 + CAS
AQS 大量使用 CAS(Unsafe
类)和自旋操作,例如入队时通过 CAS 保证线程安全:private Node enq(final Node node) {for (;;) { // 自旋直到成功Node t = tail;if (t == null) { // 队列为空,初始化头节点if (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}} }
-
线程阻塞与唤醒
使用LockSupport.park()
和LockSupport.unpark(thread)
控制线程的阻塞与唤醒,避免优先级反转问题。
技术资料大全:https://pan.q删掉汉子uark.cn/s/aa7f2473c65b
总结
AQS 是 Java 并发包的基石,通过统一的框架解决了同步器的核心问题:
- 状态管理:通过
state
字段灵活表示资源状态。 - 线程排队:CLH 队列高效管理等待线程。
- 模板方法:分离通用逻辑与具体实现,降低开发复杂度。
理解 AQS 是掌握 Java 并发编程的关键,但实际开发中建议优先使用 Java 内置的同步器(如 ReentrantLock
),而非直接继承 AQS。