AQS是什么?

程序员面试资料大全|各种技术书籍等资料-1000G

一、AQS 本质与定位

AQS(AbstractQueuedSynchronizer) 是 Java 并发包 (java.util.concurrent.locks) 的核心基础框架,它为实现阻塞锁同步器提供了底层支持。JUC 中超过 80% 的同步工具都基于 AQS 构建,包括:

  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • ReentrantReadWriteLock
  • ThreadPoolExecutor.Worker
classDiagram
    AbstractQueuedSynchronizer <|-- ReentrantLock$Sync
    AbstractQueuedSynchronizer <|-- Semaphore$Sync
    AbstractQueuedSynchronizer <|-- CountDownLatch$Sync
    ReentrantLock$Sync <|-- NonfairSync
    ReentrantLock$Sync <|-- FairSync

二、核心设计思想

1. 三大核心组件

组件 作用 实现方式
状态变量 (state) 同步状态(如锁的重入次数、信号量许可数) volatile int + CAS 操作
CLH 队列 存储等待线程的 FIFO 队列 双向链表(Node 节点)
模板方法 供子类实现的获取/释放逻辑 tryAcquire()/tryRelease() 等方法

2. CLH 队列原理

Head
Thread Node 1
Thread Node 2
Thread Node 3
Tail
  • 节点结构
    static final class Node {
        volatile int waitStatus;    // 等待状态(CANCELLED/SIGNAL/CONDITION/PROPAGATE)
        volatile Node prev;         // 前驱节点
        volatile Node next;         // 后继节点
        volatile Thread thread;     // 等待线程
        Node nextWaiter;            // 条件队列专用
    }
    
  • 自旋检测:前驱节点释放锁时唤醒后继节点(避免全局竞争)

三、关键源码解析

1. 独占模式获取锁(acquire)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&          // 子类实现获取逻辑
        acquireQueued(               // 加入等待队列
            addWaiter(Node.EXCLUSIVE), arg)) // 创建独占节点
        selfInterrupt();             // 恢复中断状态
}

2. 节点入队(addWaiter)

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); // 队列为空时初始化或CAS失败后自旋入队
    return node;
}

3. 队列中等待(acquireQueued)

final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 只有前驱是头节点才尝试获取
                setHead(node);                 // 获取成功设置为新头节点
                p.next = null; // help GC
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需阻塞
                parkAndCheckInterrupt())               // 调用LockSupport.park()
                interrupted = true;
        }
    } catch (Throwable t) {
        cancelAcquire(node); // 取消获取
        throw t;
    }
}

四、AQS 的两种模式

1. 独占模式(Exclusive)

  • 特点:同一时刻只有一个线程能获取资源
  • 应用ReentrantLock
  • 核心方法
    • acquire(int arg)
    • release(int arg)

2. 共享模式(Shared)

  • 特点:多个线程可同时获取资源
  • 应用Semaphore, CountDownLatch
  • 核心方法
    • acquireShared(int arg)
    • releaseShared(int arg)

五、AQS 在同步器中的实现差异

1. ReentrantLock

final boolean nonfairTryAcquire(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()) { // 重入逻辑
        setState(c + acquires);
        return true;
    }
    return false;
}

2. Semaphore

protected int tryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || 
            compareAndSetState(available, remaining)) // CAS扣减许可
            return remaining;
    }
}

3. CountDownLatch

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0) return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc)) // CAS减少计数
            return nextc == 0; // 计数为0时唤醒所有线程
    }
}

六、AQS 的四大关键技术

1. CAS(Compare-And-Swap)

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2. LockSupport 线程阻塞

LockSupport.park(this);   // 阻塞当前线程
LockSupport.unpark(thread); // 唤醒指定线程

3. 自旋优化

for (;;) { // 自旋避免直接阻塞
    if (/*条件满足*/) break;
    // 短暂自旋后进入阻塞
}

4. 等待状态标识

状态值 常量名 含义
1 CANCELLED 线程已取消等待
-1 SIGNAL 后继线程需要被唤醒
-2 CONDITION 线程在条件队列等待
-3 PROPAGATE 共享模式下状态需要传播

七、AQS 的进阶特性

1. 条件变量(ConditionObject)

public class ConditionObject implements Condition {
    private transient Node firstWaiter;   // 条件队列头
    private transient Node lastWaiter;    // 条件队列尾
    
    public final void await() {
        Node node = addConditionWaiter(); // 加入条件队列
        int savedState = fullyRelease(node); // 完全释放锁
        while (!isOnSyncQueue(node)) {
            LockSupport.park(this);       // 阻塞
        }
        acquireQueued(node, savedState);  // 重新竞争锁
    }
}

2. 公平 vs 非公平

策略 实现原理 优点 缺点
非公平锁 新线程直接 CAS 抢锁 高吞吐量 可能线程饥饿
公平锁 检查队列是否有等待线程才尝试获取 保证顺序性 上下文切换多

八、AQS 的最佳实践

1. 自定义同步器示例

class Mutex extends AbstractQueuedSynchronizer {
    // 尝试获取锁
    protected boolean tryAcquire(int acquires) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    
    // 尝试释放锁
    protected boolean tryRelease(int releases) {
        if (getState() == 0) throw new IllegalMonitorStateException();
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }
}

2. 使用注意事项

  1. 状态设计

    // 正确:使用原子操作管理状态
    protected final boolean compareAndSetState(int expect, int update)
    
    // 错误:直接修改state
    setState(1); // 非线程安全!
    
  2. 避免死锁

    lock.lock();
    try {
        // 临界区代码
    } finally {
        lock.unlock(); // 必须确保释放
    }
    
  3. 性能调优

    • 减少临界区代码量
    • 读写分离场景用 ReentrantReadWriteLock
    • 避免锁嵌套

九、AQS 的演进与替代方案

Java 版本改进

JDK 版本 AQS 改进
5 初始引入
6 优化 CLH 队列
8 移除无用的字段,减少内存占用
9 增强栈跟踪
15 优化条件队列性能

现代替代方案

  1. VarHandles(Java 9+)

    private static final VarHandle STATE;
    STATE.compareAndSet(this, expect, update);
    
  2. StampedLock

    StampedLock lock = new StampedLock();
    long stamp = lock.tryOptimisticRead(); // 乐观读
    
  3. Virtual Threads(Java 19+)

    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        executor.submit(() -> {
            // 轻量级线程,无需复杂同步
        });
    }
    

程序员面试资料大全|各种技术书籍等资料-1000G

AQS是什么?_第1张图片

你可能感兴趣的:(AQS是什么?)