aqs是抽象模板类,需继承并重写tryacquire/tryrelease等钩子方法来定义同步语义;state为volatile int,含义由子类解释,必须用cas操作修改;线程挂起/唤醒使用locksupport而非wait/notify;队列是带头结点的双向链表,取消节点设为cancelled并逻辑隔离。

为什么 AQS 不是直接用的类,而是被继承的模板?
因为 AQS 本身不实现具体同步语义,它只提供一套「排队 + 状态管理 + 线程唤醒」的骨架。你看到的 ReentrantLock、Semaphore、CountDownLatch 都是它的子类,靠重写 tryAcquire、tryRelease 这些钩子方法来定义“什么算获取成功”“怎么才算释放干净”。
常见错误现象:new AQS() 编译报错 —— 它是 abstract 类,不能实例化;有人试图直接调用 acquire() 却没重写 tryAcquire(),结果抛 UnsupportedOperationException。
- 所有同步逻辑必须落在子类对
tryAcquire/tryRelease等 protected 方法的实现里 -
state字段是核心状态变量,用compareAndSetState操作,不是普通赋值 - 子类通常把
AQS声明为内部类(如ReentrantLock.Sync),避免暴露同步细节
state 字段到底存什么?不同同步器用法差异在哪?
state 是一个 volatile int,但含义完全由子类解释:在 ReentrantLock 里是重入次数,在 Semaphore 里是剩余许可数,在 CountDownLatch 里是倒计数值。它不是“锁标志位”,更不是布尔开关。
容易踩的坑:setState(1) 直接赋值会破坏 CAS 安全性;或者在 tryAcquire 中仅判断 state == 0,却忽略可重入场景(比如没检查当前线程是否已持有锁)。
立即学习“Java免费学习笔记(深入)”;
- 修改
state必须用compareAndSetState或getAndIncrement等原子操作 -
ReentrantLock的非公平模式会在tryAcquire中先抢一次,失败才进队列;公平模式则跳过这步,直接排队 -
CountDownLatch的state只减不增,且await()不修改state,只挂起线程
线程挂起和唤醒靠什么?为什么不是 wait/notify?
AQS 底层用的是 LockSupport.park() 和 LockSupport.unpark(),不是对象监视器机制。这意味着它不依赖 synchronized 块,也不受 wait() 必须在同步块中调用的限制。
典型问题:调试时发现线程一直 WAITING 在 park(),但没人 unpark() —— 往往是因为 release() 逻辑没触发,或 unparkSuccessor 找不到后继节点(比如节点被取消但没清理干净)。
-
park()挂起当前线程,unpark(Thread)唤醒指定线程,两者无配对要求,可提前唤醒 - 队列中的节点用
Thread字段保存等待线程,waitStatus标记是否取消(CANCELLED = 1)或需唤醒(SIGNAL = -1) - 不要在
tryAcquire中调用park()——acquire()方法内部已封装了入队 + park 流程
CLH 队列是链表还是真正的 CLH?节点取消时会发生什么?
AQS 的队列是简化版 CLH(Craig, Landin, and Hagersten),实际是带头结点的双向链表,不是原始论文里的“隐式前驱”单向结构。头结点代表正在运行的线程,其余节点代表等待线程。
最易被忽略的细节:节点取消(比如 acquireInterruptibly 被中断)时,cancelAcquire() 会把该节点的 waitStatus 设为 CANCELLED,并跳过它重新连接前后节点 —— 但这个过程不立即删除节点,只是逻辑隔离。后续 shouldParkAfterFailedAcquire 会跳过所有 CANCELLED 前驱。
- 节点入队用
enq(),首次插入会初始化空头结点,这是唯一可能自旋 CAS 的地方 -
pred.next = node和node.prev = pred不是原子的,所以需要循环校验 - 取消节点不会从内存清除,GC 只能靠前后引用断开后自然回收,大量取消可能导致队列“虚胖”










