AQS核心原理

AQS 核心原理

AQS(AbstractQueuedSynchronizer) 是 Java 并发包的核心基石,ReentrantLock、CountDownLatch、Semaphore 等锁 / 同步工具底层都基于 AQS 实现。

它的核心设计:用一个 state 变量控制同步状态 + 一个 CLH 双向队列管理等待线程,通过 CAS 实现无锁竞争,高效实现线程阻塞 / 唤醒。


一、AQS 核心结构

AQS 最核心的就两个东西:状态变量 + 等待队列

1
2
3
4
5
6
7
8
9
10
11
public abstract class AbstractQueuedSynchronizer {
// 1. 同步状态(核心:0=无锁,>0=持有锁,重入时累加)
private volatile int state;

// 2. CLH 同步队列(双向链表,存储等待锁的线程)
private transient volatile Node head; // 头节点(哨兵节点,不存线程)
private transient volatile Node tail; // 尾节点

// 3. 等待队列(Condition 专用,单向链表)
private transient ConditionObject firstWaiter;
}

1. state 同步状态

  • volatile 修饰:保证多线程可见性
  • 含义由实现类定义:
    • ReentrantLock:0=无锁1=持有锁>1=重入次数
    • Semaphore:剩余可用许可数
    • CountDownLatch:剩余需要倒数的次数
  • 修改变量必须用 CAS(无锁原子操作),保证线程安全。

2. CLH 等待队列

  • 双向链表:每个节点封装一个等待线程
  • 节点核心属性:
    • thread:绑定的等待线程
    • waitStatus:节点状态(0 = 初始化,1 = 取消,-1 = 需要唤醒后继)
    • prev/next:前驱 / 后继指针
  • 作用:没抢到锁的线程,会被封装成节点加入队列,排队等待唤醒。

二、独占模式 vs 共享模式

AQS 支持两种同步模式,是锁 / 工具的核心区别:

1. 独占模式(Exclusive)

  • 同一时间只有一个线程能持有锁
  • 典型实现:ReentrantLock
  • 核心方法:tryAcquire()(抢锁)、tryRelease()(释放锁)

2. 共享模式(Shared)

  • 多个线程可以同时持有锁 / 许可
  • 典型实现:SemaphoreCountDownLatchReentrantReadWriteLock 读锁
  • 核心方法:tryAcquireShared()(抢共享资源)、tryReleaseShared()(释放共享资源)

三、核心逻辑:入队、出队、抢锁

独占模式

1. 抢锁逻辑

  1. 线程调用 lock(),尝试CAS 修改 state:
    • 成功 → 直接持有锁,设置锁持有者为当前线程
    • 失败 → 进入入队流程
  2. 抢锁是自旋 + CAS,避免频繁阻塞线程(提升性能)。

2. 入队逻辑

抢锁失败的线程,会被封装成 Node 加入 CLH 队列尾部:

  1. CAS 安全添加到队尾(多线程并发入队无锁)
  2. 入队后,阻塞当前线程LockSupport.park()),让出 CPU
  3. 队列是先进先出(FIFO),保证公平性(非公平锁会先插队再入队)

头节点是哨兵节点(不存线程),真正抢锁的是头节点的后继节点

3. 出队逻辑

持有锁的线程释放锁时:

  1. CAS 修改 state 为 0
  2. 唤醒头节点的后继节点LockSupport.unpark()
  3. 被唤醒的线程再次尝试抢锁:
    • 成功 → 成为新的头节点,原头节点出队
    • 失败 → 重新阻塞

共享模式

1. 抢锁逻辑

线程调用共享锁获取方法,尝试 CAS 修改 state(获取共享资源):

  • tryAcquireShared 返回 >0 → 获取成功,还有剩余资源,需要向后继节点传播唤醒

  • tryAcquireShared 返回 =0 → 获取成功,无剩余资源,不传播唤醒

  • tryAcquireShared 返回 <0 → 获取失败,进入入队流程

    抢锁是自旋 + CAS,避免频繁阻塞线程(提升性能)。

2. 入队逻辑

抢共享资源失败的线程,会被封装成 SHARED 类型 Node 加入 CLH 队列尾部:

用 CAS 安全添加到队尾(多线程并发入队无锁)

入队后,阻塞当前线程(LockSupport.park ()),让出 CPU

队列是先进先出(FIFO),头节点是哨兵节点(不存线程),真正抢资源的是头节点的后继节点。

3. 出队 + 传播唤醒逻辑

持有共享资源的线程释放锁时:

CAS 修改 state,归还共享资源

唤醒头节点的后继节点(LockSupport.unpark ())

被唤醒的线程再次尝试抢共享资源:

成功 → 成为新的头节点,原头节点出队,根据资源剩余情况继续唤醒下一个共享节点(传播唤醒)

失败 → 重新阻塞

直到资源耗尽或遇到独占节点,停止唤醒。


四、高级机制:可中断、超时等待

AQS 内置了灵活的线程控制机制,是 Java 锁比 synchronized 更强大的原因:

1. 可中断机制

  • 线程在队列等待时,可以响应中断,不会一直死等
  • 方法:lockInterruptibly()
  • 逻辑:中断信号会唤醒线程,直接抛出 InterruptedException,退出等待

2. 超时等待机制

  • 线程只等待指定时间,超时未抢到锁就返回失败
  • 方法:tryLock(long timeout, TimeUnit unit)
  • 逻辑:
    1. 线程挂起时记录超时时间
    2. 时间到自动唤醒,尝试最后一次抢锁
    3. 仍失败 → 退出队列,返回 false

五、极简流程总结

独占模式

  1. 线程 A 抢锁 → CAS 修改 state=0→1 → 成功,持有锁
  2. 线程 B 抢锁 → CAS 失败 → 封装节点入队阻塞
  3. 线程 C 抢锁 → CAS 失败 → 封装节点入队阻塞
  4. 线程 A 释放锁 → state=0 → 唤醒队列第一个等待线程
  5. 线程 B 被唤醒 → 重新抢锁 → 成功,成为新头节点

共享模式

以 Semaphore 3 个许可、5 个线程竞争为例:

  1. 线程 1/2/3 直接 CAS 抢 state 成功,直接运行;
  2. 线程 4/5 抢资源失败,封装为 SHARED 节点,追加到 CLH 队列阻塞;
  3. 线程 1 执行完毕,releaseShared 归还许可,state +1;
  4. 唤醒队列第一个等待线程 4,线程 4 抢锁成功;
  5. 因为还有剩余许可,自动传播唤醒,继续唤醒线程 5;
  6. 无剩余资源后,停止唤醒,后续线程继续排队。

AQS核心原理
http://hanqichuan.com/2026/04/15/java并发/AQS核心原理/
作者
韩启川
发布于
2026年4月15日
许可协议