诸婉丽 发表于 2025-6-3 14:17:04

深入理解 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]
查看完整版本: 深入理解 Java AQS 原理与 ReentrantLock 实现