深入理解 Java AQS 原理与 ReentrantLock 实现
目录[*]一、AQS 简介
[*]二、AQS 核心设计
[*]2.1 核心组成部分
[*]2.2 AQS 的工作原理
[*]2.3 AQS 的关键方法
[*]三、ReentrantLock 与 AQS 的关系
[*]3.1 ReentrantLock 的结构
[*]3.2 ReentrantLock 如何使用 AQS 的 state
[*]四、AQS 关键流程分析
[*]4.1 独占锁的获取流程
[*]4.2 独占锁的释放流程
[*]五、公平锁与非公平锁
[*]5.1 非公平锁(默认)
[*]5.2 公平锁
[*]六、自定义实现:简化版 ReentrantLock
[*]七、Condition 实现原理
[*]八、AQS 的应用场景
[*]九、总结
一、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()方法时,实际上会执行以下流程:
[*]首先调用 AQS 的 acquire(1)方法:
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()方法时,会执行以下流程:
[*]首先调用 AQS 的 release(1)方法:
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 唤醒等待队列中的下一个线程:
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
页:
[1]