Java并发编程进阶:彻底理解 AQS 底层原理
一、AQS整体结构
AQS(AbstractQueuedSynchronizer)是 JDK 并发包 java.util.concurrent.locks 的核心同步框架。
它本身并不实现具体锁,而是提供了一套通用机制:
- 状态管理(state)
- 线程阻塞与唤醒
- FIFO同步队列
很多并发组件都基于 AQS 实现:
- ReentrantLock
- ReentrantReadWriteLock
- Semaphore
- CountDownLatch
整体结构:

AQS的设计思想:
state负责资源状态
获取失败的线程进入队列
前驱节点释放资源后唤醒后继节点
FIFO保证公平性(公平锁)
二、核心成员变量
AQS最重要的成员变量只有三个:
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
1、state同步状态
private volatile int state;
AQS所有同步器本质都围绕state展开
例如:
ReentrantLock
state = 0 表示无锁
state = 1 表示已经被占用
state = 5 表示重入5次
Semaphore
state = 3 表示还有3个许可证
CountDownLatch
state = 10 表示还有10个任务未完成
修改state全部依赖CAS:
compareAndSetState(expect, update)
底层:
Unsafe.compareAndSwapInt(...)
2、head 同步队列头节点
private transient volatile Node head;
特点:
head通常是哨兵节点
不对应真实线程
真正等待线程从head.next开始
例如:
head -> T1 -> T2 -> T3
head为空节点。
3、tail 同步队列尾节点
private transient volatile Node tail;
所有新节点都从尾部插入形成双向链表:
head -> T1 -> T2 -> T3
↑
tail
三、AQS在JUC中的位置
AQS是锁吗?
为什么源码里总看到sync.acquire()?
实际上:
AQS 并不是锁
它只是 JDK 提供的一个同步器框架,用来解决以下通用问题:
- 线程排队
- 线程阻塞
- 线程唤醒
- 同步状态管理
真正对外提供锁功能的是 ReentrantLock
整体关系如下:
Lock接口
▲
│
ReentrantLock
│
▼
Sync
│
▼
AbstractQueuedSynchronizer
源码:
public class ReentrantLock implements Lock {
private final Sync sync;
}
其中:
abstract static class Sync
extends AbstractQueuedSynchronizer {
}
说明:
ReentrantLock
↓
Sync
↓
AQS
AQS负责同步框架
Sync负责具体同步规则
ReentrantLock负责向外提供 lock()/unlock() API
四、AQS的设计模式——模板方法模式
AQS最大的设计亮点就是模板方法模式
AQS规定了:
acquire()
release()
整个获取资源和释放资源的流程
tryAcquire()
tryRelease()
交给子类实现
例如:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg)
实际上会执行子类实现的方法
例如:
ReentrantLock.Sync.tryAcquire()
因此:
AQS负责:
怎么排队
怎么阻塞
怎么唤醒
子类负责:
什么情况下获取成功
什么情况下释放成功
结构如下:
AQS
acquire()
release()│
▼
tryAcquire()
tryRelease()│
▼
ReentrantLock
Semaphore
CountDownLatch
这也是 AQS 能够支撑大量同步器实现的根本原因
五、AQS 是干什么的
AQS(AbstractQueuedSynchronizer)看着很复杂,实际上它只解决一个问题:
多个线程竞争资源时,如何管理等待线程?
例如:
ReentrantLock lock = new ReentrantLock();
lock.lock();
假设:
Thread-A 获取成功
Thread-B 获取失败
Thread-C 获取失败
会产生一下问题:
B怎么办?
C怎么办?
谁先获得锁?
怎么挂起?
怎么唤醒?
这些事情全部由 AQS 完成,所以可以先记住:
AQS = 锁的排队管理器
AQS只管理两样东西:
state
队列
结构:
AQS
│
┌──────┴──────┐
│ │
state Queue
1、state表示资源状态
private volatile int state;
例如:
ReentrantLock
state = 0 表示无锁
state = 1 表示已经被占用
state = 5 表示重入5次
Semaphore
state = 3 表示还有3个许可证
CountDownLatch
state = 10 表示还有10个任务未完成
因此可以理解为state = 资源数量
2、Queue
获取资源失败的线程进入队列等待
例如:
Thread-A 获取锁
Thread-B 失败
Thread-C 失败
形成:
head -> B -> C
六、为什么需要 state
假设没有 state
线程来了:
A 获取锁
系统根本不知道锁是否被占用
所以需要一个变量记录
volatile int state;
例如
state=0表示没人占用
state = 0
state=1表示有人占用
state = 1
线程获取锁:
CAS(state,0,1)
成功则修改state=1代表拿到锁
失败则进入队列
所以:
state负责资源管理
七、为什么需要队列?
假设:A 持有锁
此时:B来、C来、D来 ——> 全部失败
如果没有队列:
B一直循环抢锁
C一直循环抢锁
D一直循环抢锁
CPU直接爆炸,所以:
抢不到
↓
排队
↓
睡觉
形成:
head -> B -> C -> D
这就是 AQS 队列存在的意义
八、AQS队列是什么?
AQS采用:
CLH变体双向队列
源码:
private transient volatile Node head;
private transient volatile Node tail;
队列结构:
head
↓
Node(B)
↓
Node(C)
↓
Node(D)
实际上是双向链表:
head <-> B <-> C <-> D
每个节点:
static final class Node {
Node prev;
Node next;
Thread thread;
}
保存:
当前线程
前驱节点
后继节点
九、获取锁流程
例如ReentrantLock:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
进入:
acquire(1);
流程:
tryAcquire()
尝试抢锁
成功:结束
失败:创建Node
入队:
head -> B -> C
挂起——>线程睡觉:
LockSupport.park();
十、为什么不是立即抢锁
假设:
head -> B -> C -> D
锁释放后
如果B C D一起抢,导致CPU竞争非常激烈
AQS规定:
只有head.next
才能尝试获取锁
也就是说只有B能抢
源码:
if (p == head &&
tryAcquire(arg))
这里p == head
就是我是队首
这样减少大量竞争
十一、释放锁流程
例如:
ReentrantLock.unlock();
public void unlock() {
sync.release(1);
}
进入release方法:
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()方法修改state
例如 state = 0
然后 unparkSuccessor(head);
唤醒 head.next
例如:
head -> B -> C -> D
唤醒:B
B被唤醒后:
成功:成为新head
结果:head -> C -> D
更多推荐
所有评论(0)