AQS(AbstractQueuedSynchronizer)是 Java 并发包中绝大多数同步器(如 ReentrantLock、Semaphore、CountDownLatch 等)的基础框架。它的核心原理可以概括为:用 volatile 变量表示同步状态,配合一个 FIFO 的线程等待队列,通过 CAS 和 park/unpark 实现线程的阻塞与唤醒

下面从几个关键部分说明其内部原理。


1. 同步状态:State

  • AQS 内部维护一个 volatile int state,表示共享资源的状态。

  • 子类通过重写以下方法来定义状态的获取与释放语义:

    • tryAcquire(int arg) / tryRelease(int arg) —— 独占模式

    • tryAcquireShared(int arg) / tryReleaseShared(int arg) —— 共享模式

    • isHeldExclusively() —— 是否被当前线程独占

  • 操作 state 时使用 CAS(compareAndSetState)来保证原子性,避免使用重量级锁。


2. CLH 变体队列(同步队列)

AQS 内部维护一个 FIFO 的双向链表队列(基于 CLH 锁队列的变体),用于存放获取锁失败的线程。

节点结构(Node)

每个等待线程被封装为一个 Node,关键属性:

  • thread:等待的线程。

  • waitStatus:节点状态,核心值有:

    • CANCELLED(1):节点因超时或中断被取消。

    • SIGNAL(-1):后继节点需要被唤醒,当前节点释放锁时必须唤醒后继。

    • CONDITION(-2):节点在条件队列中等待。

    • PROPAGATE(-3):共享模式下,唤醒动作需要向后传播。

  • prev / next:指向前驱和后继节点。

入队操作
  • 线程获取锁失败时,用 CAS 自旋地将新节点插入队列尾部(enq 方法),保证线程安全。

出队和唤醒
  • 队列的首节点(head)是当前持有锁的线程对应的节点。

  • 头节点释放锁后,唤醒其后继节点(unparkSuccessor),被唤醒的线程会尝试再次获取锁,成功后将自身设为新的头节点。


3. 独占模式原理

以 ReentrantLock 的 lock() 为例,最终调用到 AQS 的 acquire(int arg)

text

acquire(arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • tryAcquire:子类实现,如尝试 CAS 将 state 从 0 改为 1。

  • 失败后通过 addWaiter 将当前线程包装成独占节点,CAS 加入队列尾部。

  • acquireQueued 中线程进入自旋(spin)逻辑:

    • 检查前驱是否是头节点,如果是则再次尝试 tryAcquire

    • 如果不是头节点或尝试失败,则判断是否应挂起(shouldParkAfterFailedAcquire 会将前驱的 waitStatus 设为 SIGNAL,保证下次前驱释放时会唤醒自己),然后调用 LockSupport.park(this) 挂起线程。

  • 线程被唤醒后会继续自旋,直到成功获取锁,并将自己设为头节点。

释放时调用 release(int arg)

  • tryRelease 修改 state。

  • 成功后若头节点存在且 waitStatus 不为 0(比如为 SIGNAL),则调用 unparkSuccessor 唤醒后继节点。


4. 共享模式原理

acquireShared(int arg) 的逻辑类似,但能支持多个线程同时访问:

  • 调用 tryAcquireShared 返回负值表示失败,加入共享节点。

  • 在 doAcquireShared 中自旋,成功后调用 setHeadAndPropagate 将当前节点设为头节点,并在满足条件时连续唤醒后继共享节点,实现“传播”效应(例如 Semaphore 释放一个许可,可能同时唤醒多个等待线程)。

  • 释放时 releaseShared 调用 tryReleaseShared,成功后通过 doReleaseShared 唤醒后继并继续传播。


5. 条件队列(ConditionObject)

AQS 内部的 ConditionObject 实现了 Condition 接口,原理是维护另一套条件等待队列(单向链表),与同步队列配合:

  • await()

    • 将当前线程封装为条件节点(waitStatus = CONDITION)加入条件队列尾部。

    • 完全释放同步状态(release),让出锁。

    • 挂起当前线程,直到被 signal 唤醒或中断。

  • signal()

    • 将条件队列中的首节点转移到同步队列尾部(transferForSignal),并设置前驱的 waitStatus 为 SIGNAL

    • 这样,当锁被释放时,该线程就能在同步队列中被唤醒并重新竞争锁。


6. 设计特点总结

  • 模板方法模式:AQS 搭好了整个排队、挂起、唤醒的框架,子类只需实现判断能否获取/释放的几个 try 方法,极大地简化了同步器的开发。

  • 无锁化的队列操作:入队、修改 waitStatus 等全部基于 CAS + 自旋,避免对队列的操作加全局锁,性能极高。

  • 高效的阻塞与唤醒:利用 LockSupport.park/unpark 进行精准的线程挂起和唤醒,比 wait/notify 更轻量且无需在同步块内使用。

  • 公平性支持:AQS 本身不强制公平,公平锁的实现是在 tryAcquire 时调用 hasQueuedPredecessors() 判断是否有前驱等待节点。

简而言之,AQS 的原理就是:用 int state 表示资源,获取不到资源的线程加入 CLH 变体的 FIFO 队列,通过 CAS 保证原子变更,并通过 park/unpark 实现高效阻塞和唤醒。子类只需定义资源的获取与释放逻辑,就能快速构建出功能完备的同步组件。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐