找回密码
 立即注册
首页 业界区 安全 AQS原理剖析

AQS原理剖析

沦嘻亟 2025-6-1 21:33:44
深度剖析 AQS(AbstractQueuedSynchronizer)核心原理

AQS(AbstractQueuedSynchronizer)是 Java 并发包中最重要的基础组件之一,它是构建锁和其他同步工具的核心框架。ReentrantLock、Semaphore、CountDownLatch 等工具都是基于 AQS 实现的。下面我们将从基础概念、核心数据结构、源码剖析等方面,层层递进地深入讲解 AQS 的原理。
1. AQS 的核心思想

AQS 的核心思想是:

  • 通过一个共享的 state 变量来表示同步状态
  • 通过一个 FIFO 队列(CLH 队列)来管理等待线程
  • 通过 CAS 操作来实现线程安全的 state 更新
AQS 的设计采用了模板方法模式,开发者只需要实现 tryAcquire、tryRelease 等方法,AQS 会自动处理线程的排队和唤醒。
2. AQS 的核心数据结构

2.1 同步状态(state)


  • state:一个 volatile 修饰的 int 变量,表示同步状态。不同的同步工具对 state 的解释不同:

    • ReentrantLock:state 表示锁的重入次数。
    • Semaphore:state 表示剩余的许可数。
    • CountDownLatch:state 表示剩余的计数。

2.2 CLH 队列


  • CLH 队列:一个双向链表,用于管理等待线程。每个节点(Node)代表一个等待线程。
  • Node 结构
    1. static final class Node {
    2.     volatile int waitStatus; // 等待状态
    3.     volatile Node prev;       // 前驱节点
    4.     volatile Node next;       // 后继节点
    5.     volatile Thread thread;   // 等待线程
    6.     Node nextWaiter;          // 条件队列的后继节点
    7. }
    复制代码

    • waitStatus:表示节点的状态,如 CANCELLED(取消)、SIGNAL(需要唤醒后继节点)等。
    • prev 和 next:用于构建双向链表。
    • thread:等待的线程。

3. AQS 的核心方法

AQS 的核心方法是 acquire 和 release,它们分别用于获取和释放同步状态。
3.1 acquire 方法

acquire 方法用于获取同步状态,如果获取失败,则线程进入等待队列。
  1. public final void acquire(int arg) {
  2.     if (!tryAcquire(arg) && // 尝试获取同步状态
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入等待队列并自旋
  4.         selfInterrupt(); // 如果线程在等待过程中被中断,则恢复中断状态
  5. }
复制代码

  • tryAcquire:由子类实现,尝试获取同步状态。
  • addWaiter:将当前线程包装成 Node 并加入等待队列。
  • acquireQueued:线程在队列中自旋,直到获取同步状态。
3.2 release 方法

release 方法用于释放同步状态,并唤醒后继节点。
  1. public final boolean release(int arg) {
  2.     if (tryRelease(arg)) { // 尝试释放同步状态
  3.         Node h = head;
  4.         if (h != null && h.waitStatus != 0)
  5.             unparkSuccessor(h); // 唤醒后继节点
  6.         return true;
  7.     }
  8.     return false;
  9. }
复制代码

  • tryRelease:由子类实现,尝试释放同步状态。
  • unparkSuccessor:唤醒后继节点的线程。
4. AQS 的源码剖析

4.1 addWaiter 方法

addWaiter 方法将当前线程包装成 Node 并加入等待队列。
  1. private Node addWaiter(Node mode) {
  2.     Node node = new Node(Thread.currentThread(), mode); // 创建节点
  3.     Node pred = tail;
  4.     if (pred != null) { // 如果队列不为空,尝试快速插入
  5.         node.prev = pred;
  6.         if (compareAndSetTail(pred, node)) { // CAS 更新尾节点
  7.             pred.next = node;
  8.             return node;
  9.         }
  10.     }
  11.     enq(node); // 如果快速插入失败,则进入完整入队流程
  12.     return node;
  13. }
复制代码

  • compareAndSetTail:CAS 操作,确保线程安全地更新尾节点。
  • enq:完整入队流程,确保节点成功加入队列。
4.2 acquireQueued 方法

acquireQueued 方法让线程在队列中自旋,直到获取同步状态。
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         boolean interrupted = false;
  5.         for (;;) {
  6.             final Node p = node.predecessor(); // 获取前驱节点
  7.             if (p == head && tryAcquire(arg)) { // 如果前驱是头节点且成功获取同步状态
  8.                 setHead(node); // 将当前节点设为头节点
  9.                 p.next = null; // 断开旧头节点
  10.                 failed = false;
  11.                 return interrupted;
  12.             }
  13.             if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需要阻塞
  14.                 parkAndCheckInterrupt()) // 阻塞线程并检查中断状态
  15.                 interrupted = true;
  16.         }
  17.     } finally {
  18.         if (failed)
  19.             cancelAcquire(node); // 如果失败,则取消获取
  20.     }
  21. }
复制代码

  • shouldParkAfterFailedAcquire:检查是否需要阻塞线程。
  • parkAndCheckInterrupt:阻塞线程并检查中断状态。
4.3 unparkSuccessor 方法

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
您需要登录后才可以回帖 登录 | 立即注册