AQS(AbstractQueuedSynchronizer)深度解剖:从“奶茶店排队”到源码级设计哲学

一、AQS 是什么?

一句话定义

AQS 是 Java 并发包的"扫地僧",默默支撑了 ReentrantLockSemaphoreCountDownLatch 等一众同步器的底层实现。

核心思想

  • 用 state 表示资源 :比如锁的持有次数(ReentrantLock)或剩余许可证数量(Semaphore)。
  • 用队列管理线程:抢不到资源的线程排队等待,避免无脑自旋浪费 CPU(类似奶茶店叫号系统)。

经典比喻

  • state:奶茶店剩余的"椰果奶茶"数量。
  • CLH 队列:排队等奶茶的小哥哥小姐姐们(线程)。
  • CAS 操作:店员(CPU)用"无接触扫码枪"快速处理订单(线程竞争)。

二、AQS 核心数据结构:状态 + 队列

1. state(资源状态)

  • volatile int:保证多线程可见性。
  • 具体含义由子类定义
    • ReentrantLock:state=0 表示未加锁,>0 表示锁被重入的次数。
    • Semaphore:state 表示剩余许可证数量。
    • CountDownLatch:state 表示倒计时的初始值。

2. CLH 队列(线程排队系统)

  • 双向链表:每个节点(Node)封装一个等待线程。
  • 设计目标
    • 公平性:先来后到(公平模式下)。
    • 高效唤醒:快速找到下一个可执行的线程。
  • 节点状态(waitStatus)
    • CANCELLED(1):舔狗放弃等待(比如线程超时或中断)。
    • SIGNAL(-1):当前节点释放资源后需要唤醒下一个节点。
    • CONDITION(-2):节点在条件队列中等待(如 Condition.await())。

三、AQS 工作流程:以 ReentrantLock 为例

1. 加锁(acquire)

java 复制代码
// ReentrantLock.NonfairSync 的 lock() 方法(非公平模式)  
final void lock() {  
    if (compareAndSetState(0, 1))  // 直接插队尝试抢锁  
        setExclusiveOwnerThread(Thread.currentThread());  
    else  
        acquire(1);  // 进入AQS排队逻辑  
}  

// AQS 的 acquire() 方法  
public final void acquire(int arg) {  
    if (!tryAcquire(arg) &&  // 子类实现抢锁逻辑  
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
        selfInterrupt();  
}  

步骤拆解

  1. tryAcquire():子类自定义抢锁逻辑(比如非公平锁直接插队)。
  2. addWaiter():将线程包装为 Node,加入队列尾部(CAS 保证线程安全)。
  3. acquireQueued():在队列中自旋或阻塞,等待被唤醒。

2. 解锁(release)

java 复制代码
// ReentrantLock 的 unlock() 方法  
public void unlock() {  
    sync.release(1);  
}  

// AQS 的 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;  
}  

关键操作

  • unparkSuccessor():找到队列中第一个未取消的节点,唤醒其关联的线程。

四、源码级细节:舔狗线程的"自旋与阻塞"

1. acquireQueued() 的"舔狗循环"

java 复制代码
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);  
    }  
}  

核心逻辑

  • 自旋检查:前驱节点是否是头节点?能否抢到锁?
  • 阻塞 :通过 LockSupport.park() 让线程休眠,避免 CPU 空转。

2. shouldParkAfterFailedAcquire():舔狗的自我修养

java 复制代码
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  
    int ws = pred.waitStatus;  
    if (ws == Node.SIGNAL)  // 前驱节点会通知我  
        return true;  
    if (ws > 0) {  // 前驱节点已取消,跳过它  
        do {  
            node.prev = pred = pred.prev;  
        } while (pred.waitStatus > 0);  
        pred.next = node;  
    } else {  
        // 设置前驱节点状态为 SIGNAL(让它记得唤醒我)  
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
    }  
    return false;  
}  

本质:确保自己能被正确唤醒,同时清理队列中已取消的节点。


五、AQS 设计哲学总结

1. 模板方法模式

  • 父类搭骨架:AQS 实现了排队、阻塞、唤醒的通用逻辑。
  • 子类填血肉 :子类只需实现 tryAcquiretryRelease 等钩子方法。

2. 兼顾公平与效率

  • 非公平模式:允许插队,减少线程切换开销。
  • 公平模式:严格排队,避免线程饿死。

3. 用 CAS 替代锁

  • 无锁化设计:通过 CAS 修改 state 和队列指针,减少锁竞争。

六、高频面试题

1. 为什么 AQS 用双向链表?

  • :方便快速删除已取消的节点(如超时或中断时)。单向链表只能从头遍历,双向链表支持逆向操作。

2. AQS 的共享模式与独占模式有什么区别?

  • 独占模式 :资源只能被一个线程持有(如 ReentrantLock)。
  • 共享模式 :资源可被多个线程共享(如 Semaphore),唤醒时会传播信号(如 doReleaseShared())。

3. AQS 如何实现可重入锁?

  • :通过 state 记录重入次数,每次重入 state+1,释放时 state-1,直到 state=0 才完全释放锁。

七、总结:AQS 是并发领域的"乐高积木"

  • 高扩展性:基于 AQS 可轻松实现各种同步工具。
  • 高性能:通过 CAS + CLH 队列减少锁竞争。
  • 高智商陷阱:用错了容易导致死锁或性能问题。

最后忠告

"若你理解了 AQS,Java 并发就掌握了一半;

若你精通了 AQS,面试官会颤抖着给你发 offer!"

相关推荐
程序员JerrySUN几秒前
全面理解 Linux 内核性能问题:分类、实战与调优策略
java·linux·运维·服务器·单片机
糯米导航4 分钟前
Java毕业设计:办公自动化系统的设计与实现
java·开发语言·课程设计
糯米导航7 分钟前
Java毕业设计:WML信息查询与后端信息发布系统开发
java·开发语言·课程设计
米粉030525 分钟前
深入剖析Nginx:从入门到高并发架构实战
java·运维·nginx·架构
简诚28 分钟前
HttpURLConnection实现
java
androidwork1 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
陈小桔1 小时前
限流算法java实现
java
黑客老李2 小时前
JavaSec | SpringAOP 链学习分析
java·运维·服务器·开发语言·学习·apache·memcached
勤奋的知更鸟2 小时前
Java编程之原型模式
java·开发语言·原型模式
叶 落2 小时前
[Java 基础]数组
java·java 基础