马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
目录
一、AQS 简介
AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包(java.util.concurrent)中最核心的底子组件之一,它为 Java 中的大多数同步类(如 ReentrantLock、Semaphore、CountDownLatch 等)提供了一个通用的框架。理解 AQS 的工作原理对于深入掌握 Java 并发编程至关重要。
AQS 的作用是解决同步器的实现问题,它将复杂的同步器实现分解为简单的框架方法,开发者只需要实现少量特定的方法就能快速构建出可靠的同步器。
二、AQS 核心设计
2.1 核心组成部分
AQS 重要由以下部分组成:
- 同步状态(state):使用 volatile int 类型的变量表示资源的可用状态
- FIFO 等待队列:使用双向链表实现的队列,用于管理等待获取资源的线程
- 独占/共享模式:支持独占锁(如 ReentrantLock)和共享锁(如 CountDownLatch)两种模式
- 条件变量:通过 ConditionObject 类提供条件等待/通知机制,类似于 Object.wait()/notify()
2.2 AQS 的工作原理
AQS 通过模板方法模式,将一些通用的同步操作封装在框架内部,而将特定同步器的特性(如资源是否可获取的判断)交给子类去实现。AQS 提供以下基本操作:
- 资源获取:线程尝试获取资源,如果获取不到,将被包装成 Node 加入等待队列并被阻塞
- 资源释放:持有资源的线程释放资源后,会唤醒等待队列中的下一个线程
- 线程阻塞与唤醒:通过 LockSupport 的 park/unpark 机制实现
2.3 AQS 的关键方法
AQS 定义了一组需要子类实现的方法:
- tryAcquire(int):尝试以独占模式获取资源
- tryRelease(int):尝试以独占模式释放资源
- tryAcquireShared(int):尝试以共享模式获取资源
- tryReleaseShared(int):尝试以共享模式释放资源
- isHeldExclusively():判断资源是否被当前线程独占
三、ReentrantLock 与 AQS 的关系
ReentrantLock 是基于 AQS 实现的可重入锁,它通过内部类 Sync(继承自 AQS)来实现锁的基本功能,并通过 FairSync 和 NonfairSync 两个子类分别实现公平锁和非公平锁。
3.1 ReentrantLock 的布局
- public class ReentrantLock implements Lock {
- private final Sync sync;
- abstract static class Sync extends AbstractQueuedSynchronizer {
- // 实现锁的基本操作
- }
- // 公平锁实现
- static final class FairSync extends Sync { ... }
- // 非公平锁实现
- static final class NonfairSync extends Sync { ... }
- }
复制代码 3.2 ReentrantLock 如何使用 AQS 的 state
ReentrantLock 使用 AQS 的 state 字段来表示锁的持有次数:
- state = 0:表示锁未被持有
- state > 0:表示锁被持有,值表示重入次数
四、AQS 关键流程分析
4.1 独占锁的获取流程
当线程调用 ReentrantLock.lock()方法时,实际上会执行以下流程:
- public final void acquire(int arg) {
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }
复制代码
- tryAcquire 尝试获取锁,这是由 ReentrantLock 的 Sync 子类实现的:
- 如果 state=0,尝试使用 CAS 将 state 设为 1,并设置当前线程为持有锁的线程
- 如果当前线程已经持有锁,则增加 state 值,实现可重入
- 其他情况下返回 false
- 如果 tryAcquire 失败,则调用 addWaiter 将当前线程封装成 Node 添加到等待队列末尾:
- private Node addWaiter(Node mode) {
- Node node = new Node(Thread.currentThread(), mode);
- // 尝试快速添加到队列尾部
- Node pred = tail;
- if (pred != null) {
- node.prev = pred;
- if (compareAndSetTail(pred, node)) {
- pred.next = node;
- return node;
- }
- }
- // 快速添加失败,进入完整的入队方法
- enq(node);
- return node;
- }
复制代码
- 然后执行 acquireQueued 方法,让该节点在队列中不断尝试获取锁,直到成功或被中断:
- final boolean acquireQueued(final Node node, int arg) {
- boolean failed = true;
- try {
- boolean interrupted = false;
- for (;;) {
- // 获取前驱节点
- final Node p = node.predecessor();
- // 如果前驱是头节点,说明轮到当前节点尝试获取锁
- if (p == head && tryAcquire(arg)) {
- // 获取成功,把当前节点设为头节点
- setHead(node);
- p.next = null; // help GC
- failed = false;
- return interrupted;
- }
- // 判断是否应该阻塞当前线程
- if (shouldParkAfterFailedAcquire(p, node) &&
- parkAndCheckInterrupt())
- interrupted = true;
- }
- } finally {
- if (failed)
- cancelAcquire(node);
- }
- }
复制代码 4.2 独占锁的释放流程
当线程调用 ReentrantLock.unlock()方法时,会执行以下流程:
- 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 尝试释放锁,这是由 ReentrantLock 的 Sync 类实现的:
- 检查当前线程是否是持有锁的线程
- 减少 state 值
- 如果 state 变为 0,清空持有锁的线程,并返回 true
- 如果 tryRelease 返回 true,表示已完全释放锁,则调用 unparkSuccessor 唤醒等待队列中的下一个线程:
[code]private void unparkSuccessor(Node node) { // 获取当前节点的等待状态 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 找到下一个需要唤醒的节点 Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; // 从尾部向前查找需要唤醒的节点 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus |