一、AQS 是什么?
一句话定义 :
AQS 是 Java 并发包的"扫地僧",默默支撑了 ReentrantLock、Semaphore、CountDownLatch 等一众同步器的底层实现。
核心思想:
- 用 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();  
}  步骤拆解:
- tryAcquire():子类自定义抢锁逻辑(比如非公平锁直接插队)。
- addWaiter():将线程包装为 Node,加入队列尾部(CAS 保证线程安全)。
- 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 实现了排队、阻塞、唤醒的通用逻辑。
- 子类填血肉 :子类只需实现 tryAcquire、tryRelease等钩子方法。
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!"