AQS原理剖析

深度剖析 AQS(AbstractQueuedSynchronizer)核心原理

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


1. AQS 的核心思想

AQS 的核心思想是:

  • 通过一个共享的 state 变量来表示同步状态
  • 通过一个 FIFO 队列(CLH 队列)来管理等待线程
  • 通过 CAS 操作来实现线程安全的 state 更新

AQS 的设计采用了模板方法模式,开发者只需要实现 tryAcquiretryRelease 等方法,AQS 会自动处理线程的排队和唤醒。


2. AQS 的核心数据结构

2.1 同步状态(state)

  • state:一个 volatile 修饰的 int 变量,表示同步状态。不同的同步工具对 state 的解释不同:
    • ReentrantLock:state 表示锁的重入次数。
    • Semaphore:state 表示剩余的许可数。
    • CountDownLatch:state 表示剩余的计数。

2.2 CLH 队列

  • CLH 队列:一个双向链表,用于管理等待线程。每个节点(Node)代表一个等待线程。
  • Node 结构
    static final class Node {
        volatile int waitStatus; // 等待状态
        volatile Node prev;       // 前驱节点
        volatile Node next;       // 后继节点
        volatile Thread thread;   // 等待线程
        Node nextWaiter;          // 条件队列的后继节点
    }
    
    • waitStatus:表示节点的状态,如 CANCELLED(取消)、SIGNAL(需要唤醒后继节点)等。
    • prevnext:用于构建双向链表。
    • thread:等待的线程。

3. AQS 的核心方法

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

3.1 acquire 方法

acquire 方法用于获取同步状态,如果获取失败,则线程进入等待队列。

public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 尝试获取同步状态
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入等待队列并自旋
        selfInterrupt(); // 如果线程在等待过程中被中断,则恢复中断状态
}
  • tryAcquire:由子类实现,尝试获取同步状态。
  • addWaiter:将当前线程包装成 Node 并加入等待队列。
  • acquireQueued:线程在队列中自旋,直到获取同步状态。

3.2 release 方法

release 方法用于释放同步状态,并唤醒后继节点。

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:由子类实现,尝试释放同步状态。
  • unparkSuccessor:唤醒后继节点的线程。

4. AQS 的源码剖析

4.1 addWaiter 方法

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)) { // CAS 更新尾节点
            pred.next = node;
            return node;
        }
    }
    enq(node); // 如果快速插入失败,则进入完整入队流程
    return node;
}
  • compareAndSetTail:CAS 操作,确保线程安全地更新尾节点。
  • enq:完整入队流程,确保节点成功加入队列。

4.2 acquireQueued 方法

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; // 断开旧头节点
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需要阻塞
                parkAndCheckInterrupt()) // 阻塞线程并检查中断状态
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node); // 如果失败,则取消获取
    }
}
  • shouldParkAfterFailedAcquire:检查是否需要阻塞线程。
  • parkAndCheckInterrupt:阻塞线程并检查中断状态。

4.3 unparkSuccessor 方法

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 <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread); // 唤醒线程
}
  • compareAndSetWaitStatus:CAS 操作,清除节点状态。
  • LockSupport.unpark:唤醒线程。

5. AQS 的应用

5.1 ReentrantLock 的实现

ReentrantLock 是基于 AQS 实现的独占锁。它的 tryAcquiretryRelease 方法如下:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) { // 如果锁未被占用
        if (compareAndSetState(0, acquires)) { // CAS 获取锁
            setExclusiveOwnerThread(current); // 设置独占线程
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) { // 如果锁已被当前线程占用
        int nextc = c + acquires; // 重入次数加 1
        if (nextc < 0) // 溢出检查
            throw new Error("Maximum lock count exceeded");
        setState(nextc); // 更新 state
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases; // 重入次数减 1
    if (Thread.currentThread() != getExclusiveOwnerThread()) // 检查当前线程是否持有锁
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { // 如果锁完全释放
        free = true;
        setExclusiveOwnerThread(null); // 清除独占线程
    }
    setState(c); // 更新 state
    return free;
}

5.2 Semaphore 的实现

Semaphore 是基于 AQS 实现的共享锁。它的 tryAcquireSharedtryReleaseShared 方法如下:

protected int tryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || compareAndSetState(available, remaining)) // CAS 更新 state
            return remaining;
    }
}

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // 溢出检查
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next)) // CAS 更新 state
            return true;
    }
}

6. 总结

AQS 的核心原理可以总结为以下几点:

  1. 同步状态:通过 state 变量表示同步状态。
  2. 等待队列:通过 CLH 队列管理等待线程。
  3. CAS 操作:通过 CAS 实现线程安全的 state 更新。
  4. 模板方法:子类实现 tryAcquiretryRelease 等方法,AQS 负责线程排队和唤醒。

通过深入理解 AQS 的原理,可以更好地掌握 Java 并发工具的实现机制,并能够灵活地实现自定义的同步工具。

posted @   佛祖让我来巡山  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)

佛祖让我来巡山博客站 - 创建于 2018-08-15

开发工程师个人站,内容主要是网站开发方面的技术文章,大部分来自学习或工作,部分来源于网络,希望对大家有所帮助。

Bootstrap中文网

点击右上角即可分享
微信分享提示