aqs是抽象模板框架而非直接可用的锁,需通过子类重写tryacquire/tryrelease等方法定义资源语义;其state为volatile int,须用cas原子更新;clh队列为无长度双向链表,不支持遍历;独占与共享模式不可混用。

AQS 是 Java 并发包(java.util.concurrent)里真正干活的“底层引擎”,不是现成的锁,而是让你能快速造出锁、信号量、栅栏等同步工具的模板框架。
为什么不能直接 new AQS()?它根本不是拿来直接用的
AQS 是一个 abstract class,所有核心逻辑都靠子类实现。你调用的 ReentrantLock、Semaphore、CountDownLatch,背后全是它在调度线程排队和唤醒——但你永远看不到它被直接实例化。
- 它不负责定义“什么算抢到资源”,只管“抢不到就排队、释放就唤醒”
- 真正决定逻辑的是子类重写的
tryAcquire()和tryRelease()这类方法 - 如果你试图 new AQS(),编译器会立刻报错:
AbstractQueuedSynchronizer is abstract; cannot be instantiated
state 变量怎么用?别把它当成普通 int
state 是 AQS 唯一的状态载体,类型是 volatile int,但它不是让你随便读写的地方。
- 必须用
compareAndSetState(expect, update)做原子更新;直接setState()仅用于已知无竞争的场景(如释放锁后清零) - 不同同步器对
state的语义完全不同:ReentrantLock用它记重入次数,Semaphore当剩余许可数,CountDownLatch当倒计数值 - 误用
getState() == 0判断锁是否空闲?危险!因为state可能被其他线程同时修改,必须结合 CAS 操作做条件判断
CLH 队列不是 LinkedList,别想用遍历或 size() 查队列
AQS 的等待队列是 CLH 的变体:一个懒初始化、无长度字段、仅靠 head/tail 节点指针维系的双向链表。它没有 size() 方法,也不支持随机访问。
立即学习“Java免费学习笔记(深入)”;
-
tail不一定代表“最后一个排队者”——可能刚入队还没完成 prev 指针链接,此时读tail.next会是 null - 不要循环遍历队列去“找某个线程”,AQS 不提供这种能力;它的设计目标是低开销的 FIFO 唤醒,不是线程管理容器
- 调试时看到
Node.waitStatus == 1(即CANCELLED),说明该节点对应线程已中断/超时,会被跳过,不会参与后续唤醒
独占 vs 共享模式混用?子类必须选一边站队
AQS 内部严格区分两种资源获取方式,且大多数标准实现(包括 ReentrantReadWriteLock)都是靠两个独立的 AQS 子类分别实现读/写,而不是在一个类里同时支持。
- 独占模式走
acquire()/release(),配套tryAcquire()/tryRelease() - 共享模式走
acquireShared()/releaseShared(),配套tryAcquireShared()/tryReleaseShared() - 如果子类同时重写了两套 try 方法,但没控制好调用路径,极可能引发状态混乱或唤醒丢失——比如
tryAcquireShared()返回负值却没处理阻塞逻辑
最常被忽略的一点:AQS 的唤醒不是“广播”,而是精确唤醒头节点的后继;如果头节点被取消、或后继节点也已取消,就得继续往后找——这个查找过程是自旋 + CAS 的,写错 shouldParkAfterFailedAcquire() 逻辑,线程就会永久挂起。









