Java 并发核心:AQS(AbstractQueuedSynchronizer) 详解

发布于:2025-07-05 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、什么是 AQS?

AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包 java.util.concurrent.locks 中的一个核心同步框架,用于构建锁和同步器,如:

  • ReentrantLock
  • ReentrantReadWriteLock
  • CountDownLatch
  • Semaphore
  • FutureTask

AQS 通过一个FIFO 双向等待队列(CLH 队列)管理线程的同步状态,使开发者可以专注于同步逻辑,而不必关注线程的调度、阻塞、唤醒等底层细节。


二、AQS 的核心设计

1. 核心成员变量

private volatile int state;       // 同步状态
private transient Node head;      // 队列头节点
private transient Node tail;      // 队列尾节点
  • state:用来表示资源的占用状态(例如是否被锁定、可用信号量数等);
  • headtail:维护一个 CLH 等待队列,用于管理阻塞的线程。

2. 核心方法(模板方法)

AQS 提供一系列模板方法用于子类实现:

// 共享模式
protected int tryAcquireShared(int arg);
protected boolean tryReleaseShared(int arg);

// 独占模式
protected boolean tryAcquire(int arg);
protected boolean tryRelease(int arg);

子类需要实现这些方法,以控制对资源的获取与释放逻辑。


三、AQS 工作流程

1. 获取锁(以独占模式为例)

lock.lock() → tryAcquire() 尝试获取 → 获取失败 → 加入 CLH 队列 → park(阻塞)→ 被唤醒时再次尝试

流程:

  1. 调用 tryAcquire() 尝试获取资源;
  2. 如果失败,则将当前线程封装为 Node 加入等待队列;
  3. 阻塞(park)当前线程;
  4. 资源释放后,唤醒下一个等待线程。

2. 释放锁

unlock() → tryRelease() 成功 → 唤醒队列中下一个线程
  • tryRelease()state 设置为 0;
  • 然后调用 unparkSuccessor() 唤醒下一个线程。

四、独占模式 vs 共享模式

模式 描述
独占模式 同一时刻只能有一个线程访问,如 ReentrantLock
共享模式 多个线程可以共享资源,如 Semaphore、CountDownLatch

AQS 区分这两种模式,并分别处理入队、出队、唤醒等逻辑。


五、CLH 队列机制

AQS 使用一种变体的 CLH(Craig–Landin–Hagersten)同步队列 实现线程排队。

队列结构:

head -> Node1 -> Node2 -> ... -> tail
  • 每个节点是一个线程的等待快照;
  • 新线程失败后加入尾部,前驱释放时唤醒后继;
  • 自旋或 park 阻塞等待。

六、典型用法示例

自定义锁的基本写法

class MyLock extends AbstractQueuedSynchronizer {
    protected boolean tryAcquire(int arg) {
        return compareAndSetState(0, 1);
    }

    protected boolean tryRelease(int arg) {
        setState(0);
        return true;
    }

    public void lock() {
        acquire(1); // 内部调用 tryAcquire + 入队逻辑
    }

    public void unlock() {
        release(1);
    }
}

CountDownLatch 原理

  • 使用共享模式;
  • 每次 countDown() 执行 releaseShared(-1)
  • await() 会在 state = 0 前阻塞。

七、AQS 的优点

优点 说明
封装阻塞逻辑 使用 LockSupport 封装了 park/unpark
可复用性强 模板方法模式,便于自定义同步器
队列高效 FIFO 队列实现公平性,性能稳定
支持两种模式 支持共享和独占资源控制

八、注意点 & 问题排查

  1. 死锁:一定要在 try...finally 中释放锁;
  2. state 状态:设计时需合理设置 state 的含义(计数、二进制等);
  3. CLH 队列泄露:释放时未正确调用 unpark 会导致阻塞线程永久等待;
  4. 性能瓶颈:高并发下需注意锁竞争,考虑使用 StampedLock 或乐观锁优化。

九、AQS 应用类汇总

类名 模式 简介
ReentrantLock 独占 可重入、可中断、公平/非公平
Semaphore 共享 控制资源访问数目
CountDownLatch 共享 等待所有任务完成
ReentrantReadWriteLock 共享 + 独占 高效读写分离
FutureTask 独占 控制异步任务状态
AbstractQueuedSynchronizer 基类 框架级同步器

十、总结

  • AQS 是构建 Java 同步工具的核心;
  • 通过队列管理线程阻塞与唤醒;
  • 使用模板方法封装了多种模式;
  • 掌握 AQS = 掌握 Java 并发的核心原理。

补充:AQS 源码流程图解析 + ReentrantLock 实战源码


一、AQS 获取锁(acquire)源码流程

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

流程图解:

             acquire()
                 │
         ┌───────┴────────┐
         ↓                ↓
  tryAcquire()     // 自定义尝试获取
   (成功返回true)     (失败)
         ↓                ↓
        return      addWaiter(Node.EXCLUSIVE)
                          ↓
                入队列构造双向链表
                          ↓
              acquireQueued(node, arg)
                          ↓
               while(前驱不是head || 不能获取锁)
                     ↓        ↓
                  park()   tryAcquire()
                          ↓
               获取成功 → 设置head → unpark下一个

二、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;
}

流程图解:

            release()
                │
          tryRelease()
                │
          是否成功释放?
             ↓   ↑
           true  false
             ↓
          获取 head
             ↓
     唤醒下一个节点(unpark)

三、ReentrantLock 是怎么用 AQS 实现的?

源码结构简图:

public class ReentrantLock implements Lock {
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 核心逻辑都在此子类中
    }
    final Sync sync;
}

ReentrantLock 的两个实现版本:

类型 类名 特点
非公平锁(默认) NonfairSync 直接尝试获取锁,抢占式,性能好
公平锁 FairSync 排队获取,按顺序,不插队,公平但慢一些

tryAcquire 的实现(非公平锁)

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 尝试直接 CAS 获取锁
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        // 可重入,加重入次数
        int nextc = c + acquires;
        setState(nextc);
        return true;
    }
    return false;
}

tryRelease 的实现

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

四、AQS 线程状态控制核心机制:LockSupport

AQS 底层通过 LockSupport.park()unpark(thread) 来挂起/唤醒线程:

  • park():当前线程阻塞,等待被唤醒;
  • unpark(Thread t):唤醒指定线程;
  • 这种控制机制替代了传统的 wait/notify,更灵活、底层、性能好。

五、总结:如何理解 AQS 的强大之处?

特性 说明
模板方法设计 只需实现 tryAcquiretryRelease,其他线程队列处理由 AQS 自动完成
双模式支持 支持共享与独占两种访问模型
高可复用性 可构建多种同步组件(锁、信号量、栅栏等)
高性能 结合 CAS、自旋、队列挂起唤醒机制
阻塞线程管理 使用 LockSupport 精细控制线程挂起与恢复

网站公告

今日签到

点亮在社区的每一天
去签到