引言
在 Java 多线程编程中,同步是确保线程安全的关键。Java 并发包(java.util.concurrent)提供了许多高级工具,如 ReentrantLock、Semaphore 和 CountDownLatch,这些工具的底层都依赖于一个强大的框架——AbstractQueuedSynchronizer(AQS)。AQS 由并发大师 Doug Lea 设计,是 Java 并发包的核心组件之一。它通过一个原子整数状态(state)和 FIFO 等待队列管理线程竞争,提供了独占和共享两种同步模式。
AQS 的设计目标是简化锁和同步器的开发。开发者无需从零实现复杂的线程排队和阻塞逻辑,只需继承 AQS 并实现几个关键方法即可创建自定义同步器。例如,ReentrantLock 使用 AQS 实现可重入锁,Semaphore 使用 AQS 管理许可数量。本文将从核心概念、基本使用、源码分析到高级主题,全面解析 AQS 的工作原理,并通过示例代码帮助读者理解其应用。
本文的目标是让读者不仅理解 AQS 的理论,还能通过详细的源码分析和代码示例掌握其底层机制。无论你是 Java 并发编程的初学者还是希望深入研究的高级开发者,本文都将为你提供清晰且实用的指导。
AQS 的核心概念
要理解 AQS,首先需要掌握其核心概念。以下是 AQS 的四个关键组成部分:
1. 同步状态(State)
AQS 使用一个 volatile int 类型的状态(state)来表示同步状态。这个状态的具体含义由子类定义。例如:
-
在 ReentrantLock 中,state 表示锁的持有状态(0 表示未被持有,大于 0 表示被持有,数值表示重入次数)。
-
在 Semaphore 中,state 表示可用许可的数量。
AQS 提供了以下方法操作状态:
-
getState():获取当前状态值,具有 volatile 读语义。
-
setState(int newState):设置状态值,具有 volatile 写语义。
-
compareAndSetState(int expect, int update):使用 CAS(比较并交换)原子性地更新状态。
这些方法确保状态操作的线程安全性。
2. FIFO 等待队列
AQS 使用一个基于 CLH(Craig, Landin, Hagersten)锁的变体队列来管理等待线程。这个队列是一个双向链表,包含以下关键字段:
-
head:队列头部,volatile Node 类型。
-
tail:队列尾部,volatile Node 类型。
队列中的每个节点(Node)表示一个等待线程,包含以下字段:
-
volatile int waitStatus:节点状态,可能值为:
-
CANCELLED (1):节点因超时或中断被取消。
-
SIGNAL (-1):后继节点需要被唤醒。
-
CONDITION (-2):节点在条件队列中等待。
-
PROPAGATE (-3):用于共享模式,传播释放操作。
-
0:正常状态。
-
-
volatile Node prev:前驱节点引用。
-
volatile Node next:后继节点引用。
-
volatile Thread thread:关联的线程。
-
Node nextWaiter:条件队列中的下一个节点或共享模式标记。
队列通过 compareAndSetTail 和 compareAndSetHead 实现原子性操作,确保线程安全。
3. 独占与共享模式
AQS 支持两种同步模式:
-
独占模式:一次只有一个线程可以获取同步器。例如,ReentrantLock 使用独占模式确保只有一个线程持有锁。
-
共享模式:多个线程可以同时获取同步器。例如,Semaphore 允许多个线程获取许可,直到许可耗尽。
子类通过实现以下方法定义同步 Pragmatic Play 同步逻辑:
-
独占模式:tryAcquire(int arg) 和 tryRelease(int arg)。
-
共享模式:tryAcquireShared(int arg) 和 tryReleaseShared(int arg)。
4. 条件对象(Condition Object)
在独占模式下,AQS 提供了 ConditionObject 类,实现 Condition 接口,类似于 Java 的 wait/notify 机制。条件对象允许线程在特定条件下等待(通过 await())或被唤醒(通过 signal() 或 signalAll())。每个条件对象维护一个独立的等待队列。
AQS 的基本使用
AQS 的强大之处在于其扩展性。开发者可以通过继承 AQS 并实现以下方法创建自定义同步器:
-
tryAcquire(int arg):尝试在独占模式下获取同步器。
-
tryRelease(int arg):尝试在独占模式下释放同步器。
-
tryAcquireShared(int arg):尝试在共享模式下获取同步器。
-
tryReleaseShared(int arg):尝试在共享模式下释放同步器。
-
isHeldExclusively():判断同步器是否被当前线程独占持有。
以下是一个简单的互斥锁(Mutex)实现示例:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
@Override
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, java.util.concurrent.TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}

最低0.47元/天 解锁文章
170万+

被折叠的 条评论
为什么被折叠?



