目录
介绍
在Java并发包(Java.util.concurrent)中,AbstractQueuedSynchronizer(简称AQS)是构建锁和同步器的核心框架。像ReentrantLock、CountDownLatch、Semaphore等常见同步工具,底层都依赖于AQS。
AQS核心功能是资源状态管理(通过一个整数表示同步状态,子类可以定义该状态的意义和操作方式)、线程排队管理(通过 FIFO 等待队列管理多个线程的竞争和等待)、提供模板方法(QS 提供了一系列模板方法,子类可以通过实现这些方法来定义具体的同步机制),AQS 的设计理念是将同步状态和线程队列的管理逻辑抽象出来,以便不同类型的同步器可以重用这些逻辑。这样,开发者可以集中精力于具体的同步器逻辑实现,而无需关心底层的线程排队和状态管理细节。
在JDK1.5中首次添加AQS,Java并发包的作者是Doug Lea,深入理解juc的相关理论可以参考Doug Lea写的《The_java.util.concurrent_Synchronizer_Framework》论文。
原理
AQS通过两个关键组件管理同步状态:
- volatile int state:表示共享资源的状态(如锁的重入次数、信号量许可数)。
- FIFO双向队列:存储等待资源的线程(CLH队列的变体)。
CLH锁介绍
CLH锁其实是基于队列(单向链表)排队的自旋锁,申请加锁的线程首先会通过CAS操作在单向链表的尾部增加一个节点,之后该线程只需要在其前驱节点上进行普通自旋,等待前驱节点释放锁即可。
AQS中CLH锁队列
与传统CLH锁不同处:
- 自旋+阻塞:普通CLH锁使用自旋等待锁释放,这样会占用CPU资源,AQS中短暂自旋后进入阻塞状态,等待被唤醒,减少CPU占用。
- 双向队列:AQS采用双向链表存储,节点不仅知道前驱节点,也可以唤醒后继节点,简化了队列操作,提高唤醒效率。
详细解析
原理图:
AQS通过把线程封装为Node节点,建立节点之间的关联关系来实现锁分配的,因为没有定义使用真实队列实例,所以称为虚拟的双向队列。以下是其定义(使用了jdk22中定义,jdk8中Node节点状态定义为waitStatus,且有一些状态值定义,此处不进行罗列):
// Head of the wait queue, lazily initialized
private transient volatile Node head;
// Tail of the wait queue. After initialization, modified only via casTail.
private transient volatile Node tail;
// The synchronization state.
private volatile int state;
// 节点状态标志位
static final int WAITING = 1; // 等待唤醒
static final int CANCELLED = 0x80000000; // 已取消
static final int COND = 2; // 位于条件队列
// Node节点定义
abstract static class Node {
volatile Node prev; // initially attached via casTail
volatile Node next; // visibly nonnull when signallable
Thread waiter; // visibly nonnull when enqueued
volatile int status; // written by owner, atomic bit ops by others
......
}
// state状态操作方法
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return U.compareAndSetInt(this, STATE, expect, update);
}
// 对外提供的模板方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
AQS中线程的挂起与唤起是通过LockSupport.park()和LockSupport.unpark()实现的。AQS提供两种资源共享方式,分别是独占式和共享式。
- 独占式资源访问:同一时刻仅一个线程能获取资源(如
ReentrantLock
)。 - 共享式资源访问:允许多个线程同时获取资源(如
Semaphore
、CountDownLatch
)。
源码分析
以下基于jdk22,实现与jdk8版本略有不同。
资源获取(acquire)
final int acquire(AbstractQueuedSynchronizer.Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
Thread current = Thread.currentThread();
byte spins = 0, postSpins = 0; // retries upon unpark of first thread
boolean interrupted = false, first = false;
AbstractQueuedSynchronizer.Node pred = null; // predecessor of node when enqueued
/*
* Repeatedly:
* Check if node now first
* if so, ensure head stable, else ensure valid predecessor
* if node is first or not yet enqueued, try acquiring
* else if queue is not initialized, do so by attaching new header node
* resort to spinwait on OOME trying to create node
* else if node not yet created, create it
* resort to spinwait on OOME trying to create node
* else if not yet enqueued, try once to enqueue
* else if woken from park, retry (up to postSpins times)
* else if WAITING status not set, set and retry
* else park and clear WAITING status, and check cancellation
*/
for (;;) {
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
if (pred.status < 0) {
cleanQueue(); // predecessor cancelled
continue;
} else if (pred.prev == null) {
Thread.onSpinWait(); // ensure serialization
continue;
}
}
if (first || pred == null) {
boolean acquired;
try {
if (shared)
acquired = (tryAcquireShared(arg) >= 0);
else
acquired = tryAcquire(arg);
} catch (Throwable ex) {
cancelAcquire(node, interrupted, false);
throw ex;
}
if (acquired) {
if (first) {
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
if (shared)
signalNextIfShared(node);
if (interrupted)
current.interrupt();
}
return 1;
}
}
AbstractQueuedSynchronizer.Node t;
if ((t = tail) == null) { // initialize queue
if (tryInitializeHead() == null)
return acquireOnOOME(shared, arg);
} else if (node == null) { // allocate; retry before enqueue
try {
node = (shared) ? new AbstractQueuedSynchronizer.SharedNode() : new AbstractQueuedSynchronizer.ExclusiveNode();
} catch (OutOfMemoryError oome) {
return acquireOnOOME(shared, arg);
}
} else if (pred == null) { // try to enqueue
node.waiter = current;
node.setPrevRelaxed(t); // avoid unnecessary fence
if (!casTail(t, node))
node.setPrevRelaxed(null); // back out
else
t.next = node;
} else if (first && spins != 0) {
--spins; // reduce unfairness on rewaits
Thread.onSpinWait();
} else if (node.status == 0) {
node.status = WAITING; // enable signal and recheck
} else {
long nanos;
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed)
LockSupport.park(this);
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos);
else
break;
node.clearStatus();
if ((interrupted |= Thread.interrupted()) && interruptible)
break;
}
}
return cancelAcquire(node, interrupted, interruptible);
}
关键步骤:
- 快速尝试:若节点未入队且为头节点后继,直接调用 tryAcquire / tryAcquireShared
- 队列初始化:若队列为空,创建哑节点(ExclusiveNode)
- 节点入队:将当前线程包装为节点(独占ExclusiveNode/共享SharedNode)
- 状态自旋:设置节点状态为 WAITING(等待唤醒)根据中断/超时策略选择阻塞方式(LockSupport.park)
- 唤醒后重试:被唤醒后重新尝试获取资源
- 资源获取成功:将当前节点设为头节点;断开原头节点链接(辅助GC);共享模式下唤醒后继节点
资源释放(release)
// 独占模式释放
public final boolean release(int arg) {
if (tryRelease(arg)) {
signalNext(head); // 唤醒后继节点
return true;
}
return false;
}
// 共享模式释放
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
signalNext(head); // 唤醒后继节点
return true;
}
return false;
}
private static void signalNext(AbstractQueuedSynchronizer.Node h) {
AbstractQueuedSynchronizer.Node s;
if (h != null && (s = h.next) != null && s.status != 0) {
s.getAndUnsetStatus(WAITING);
LockSupport.unpark(s.waiter);
}
}
关键操作:
signalNext()
:从后向前遍历,找到首个未取消节点并唤醒- 清除节点
WAITING
状态,避免重复唤醒
条件变量(ConditionObject)
-
await流程:
-
创建
ConditionNode
加入条件队列 -
完全释放资源(
enableWait()
) -
阻塞直到被
signal
或中断 -
被唤醒后重新竞争资源
-
signal流程:
-
将条件队列头节点移入CLH队列
-
清除节点
COND
状态 -
设置节点为
WAITING
状态 -
唤醒节点对应线程
核心流程图
性能优化点
- 延迟初始化:首次竞争时才创建头节点
- GC优化:出队节点及时断开引用(
head.next = null
) - OOME处理:资源不足时退避重试(
acquireOnOOME
) - 自旋避让:
Thread.onSpinWait()
优化多核竞争
使用场景
同步器 | 实现模式 | 关键特性 |
---|---|---|
ReentrantLock | 独占 | 可重入、公平/非公平 |
Semaphore | 共享 | 资源计数控制 |
CountDownLatch | 共享 | 一次性同步屏障 |
ReentrantReadWriteLock | 混合模式 | 读写锁分离 |
CyclicBarrier | ReentrantLock + Condition | 多个线程相互等待,支持回调,可重置 |