AQS(AbstractQueuedSynchronizer,抽象队列同步器)是 Java 并发编程中实现锁和同步器的基础框架,其核心思想是通过状态管理和等待队列实现线程的同步控制。
一、核心概念
同步状态(State):
- 一个volatile int 变量,用于表示同步状态(State = 0 表示未锁定;State > 0 表示已锁定)。
- 通过getState()、setState()、compareAndSetState()等CAS操作进行原子性修改。
等待队列(CLH变体队列):
一个双向链表,用于存储等待获取同步资源的线程。
CLH变体队列中的节点Node:
方法和属性值 | 含义 |
waitStatus | 当前节点在队列中的状态 |
thread | 表示处于该节点的线程 |
prev | 前驱指针 |
predecessor | 返回前驱节点,没有的话抛出NullPointerException |
nextWaiter | 指向下一个处于CONDITION(等待唤醒)状态的节点 |
next | 后继指针 |
waitStatus值 | |
---|---|
值 | 含义 |
0 | 当一个Node被初始化的时候的默认值 |
CANCELLED | 为 1 ,表示线程已取消等待(如超时或中断) |
CONDITION | 为 -2 ,表示节点在等待队列中,节点线程等待唤醒 |
PROPAGATE | 为 -3 ,当前献策会给你处于SHARED(共享)情况下,该字段才会使用 |
SIGNAL | 为 -1 ,表示线程已经准备好了,就等资源释放了 |
二、核心流程
AQS 的核心操作围绕获取资源和释放资源展开,具体流程如下:
1. 获取资源(Acquire)
线程尝试获取同步资源,若失败则进入等待队列并阻塞。
步骤 1:尝试获取资源
调用tryAcquire(int arg)(由子类实现,如ReentrantLock的非公平锁逻辑):
- 若成功(如状态修改为锁定),意味着当前线程通过tryAcquire(int arg)方法成功获取到了同步资源的使用权,无需进入等待队列,直接继续执行后续的业务逻辑。
- 若失败,进入步骤 2。
步骤 2:加入等待队列
创建一个新的Node节点,封装当前线程。
通过 CAS 操作将节点加入队列尾部(确保线程安全)。
步骤 3:阻塞当前线程
节点加入队列后,检查前驱节点状态:
- 若前驱是头节点且再次尝试获取资源成功,则当前节点成为新头节点,线程继续执行。
- 若失败,即当前线程不具备立即获取资源的条件(要么还没轮到自己,要么轮到了但被其他线程抢占),通过LockSupport.park()阻塞当前线程,并等待被唤醒。
2. 释放资源(Release)
持有资源的线程释放资源,唤醒队列中等待的线程。
步骤 1:尝试释放资源
调用tryRelease(int arg)(由子类实现):
- 若成功(如状态修改为 0),进入步骤 2。
- 若失败,即当前线程调用tryRelease(int arg)尝试释放资源时,由于不满足释放条件(如未持有资源、释放次数不匹配等),释放操作失败,此时 AQS 的释放流程直接终止,不会执行后续的 “唤醒等待线程” 步骤。
步骤 2:唤醒等待线程
获取头节点的后继节点(next)。
若后继节点有效(非CANCELLED,没有取消等待),通过LockSupport.unpark(next.thread)唤醒该节点对应的线程。
3. 线程唤醒后的处理
被唤醒的线程从阻塞状态恢复后,会再次尝试获取资源:
- 若成功,将自己的节点设为新头节点,继续执行。
- 若失败,重新检查前驱节点状态,必要时再次阻塞(防止虚假唤醒)。
三、AQS 的两种模式
AQS 支持独占模式(如ReentrantLock)和共享模式(如CountDownLatch),流程略有差异:
- 独占模式:资源只能被一个线程持有(tryAcquire/tryRelease)。
- 共享模式:资源可被多个线程持有(tryAcquireShared/tryReleaseShared),释放时可能唤醒多个线程。
线程的两种锁模式 | |
---|---|
模式 | 含义 |
SHARED | 表示线程以共享的模式,等待锁 |
EXCLUSIVE | 表示线程正在以独占的方式,等待锁 |
四、总结
AQS 的核心逻辑可概括为:
“状态判断→资源获取 / 释放→队列管理→线程阻塞 / 唤醒”
通过模板方法(acquire/release)定义流程,子类只需实现tryAcquire/tryRelease等抽象方法即可自定义同步逻辑,极大简化了锁和同步器的实现。
(“模板方法” 指的是 AQS 框架已经预先定义好了同步操作的整体流程(骨架),而将流程中需要自定义的具体逻辑(如 “如何获取资源”“如何释放资源”)延迟到子类中实现。
简单说,AQS 就像一个 “半成品框架”:它规定了 “获取资源→入队→阻塞→唤醒→释放资源” 的完整步骤(这就是模板方法),但把其中最关键的 “判断能否获取资源”“如何修改状态” 等细节留给子类去实现。)