state 必须是 volatile int,是为了保证所有线程对锁状态的可见性和有序性;cas操作依赖volatile提供的内存语义,且aqs内部逻辑强依赖volatile与unsafe组合。

state 变量为什么必须是 volatile int?
AQS 的核心状态靠 state 控制,它不是普通 int,而是被 volatile 修饰的——这不是为了“线程安全”,而是为了保证所有线程对锁状态的**可见性**和**有序性**。没有 volatile,一个线程修改了 state,另一个线程可能永远看不到新值,或者看到乱序写入(比如先看到 state=1,再看到初始化动作)。CAS 操作(如 compareAndSetState)本身依赖 volatile 提供的内存语义,否则 CAS 可能成功但后续读取失效。
- 常见错误:自己实现同步器时把
state改成普通int或用AtomicInteger替代——不行,AQS 内部逻辑强依赖volatile+ Unsafe 的原子操作组合 - 使用场景:
ReentrantLock用state==0表示无锁,state==1表示已占有,重入时递增;Semaphore则用state表示剩余许可数 - 性能影响:
volatile读写比普通变量略慢,但远低于加锁开销;在高竞争下,它避免了反复读到过期状态导致的无效自旋或重复入队
同步队列为什么是双向链表而不是单向或数组?
AQS 的等待队列是基于 Node 构建的双向链表(head ↔ tail),不是 CLH 的原始单向变体,也不是环形数组——这是为支持「取消节点」和「精准唤醒后继」而做的关键取舍。
- 常见错误:以为入队就是简单 tail.next = newNode → 忘记要先用 CAS 更新
tail,且失败后需重试;更常见的是忽略Node初始化时 pre 和 next 的赋值顺序,导致链表断裂 - 使用场景:当线程被中断或超时退出等待时,AQS 需将对应
Node标记为CANCELLED并从队列中逻辑剔除——双向结构才能安全跳过该节点,不影响前后节点连接 - 参数差异:每个
Node存着线程引用、等待模式(EXCLUSIVE/SHARED)、waitStatus(含CANCELLED/SIGNAL等),这些字段共同决定唤醒策略
acquire() 和 tryAcquire() 的分工到底是谁干啥?
acquire(int arg) 是 AQS 提供的模板方法,负责「兜底逻辑」:先调 tryAcquire(arg) 尝试获取,失败就入队、挂起;而 tryAcquire(arg) 必须由子类重写,只管「能不能拿」,不碰队列、不处理阻塞。
- 常见错误:在
tryAcquire里手动调LockSupport.park()或修改head/tail——这会绕过 AQS 的统一调度,造成死锁或唤醒丢失 - 使用场景:公平锁的
tryAcquire会先检查队列是否有前驱节点;非公平锁则直接 CAS 抢占,抢不到才走 acquire 流程 - 性能影响:如果
tryAcquire实现太重(比如查数据库、远程调用),会导致每次抢锁都慢,且无法利用 AQS 的快速失败路径
共享模式下 releaseShared() 为什么可能唤醒多个线程?
releaseShared(int arg) 调用子类的 tryReleaseShared(arg) 成功后,会循环调用 doReleaseShared() 唤醒后继——这不是唤醒一个,而是「传播唤醒」,直到遇到非 SIGNAL 状态的节点或队列为空。
立即学习“Java免费学习笔记(深入)”;
- 常见错误:实现
tryReleaseShared时返回 false(表示未完全释放),但没考虑 state 是否真的可被多次释放(如CountDownLatch的 count 减到 0 后必须始终返回 true) - 使用场景:
Semaphore(3)释放一个许可,state 从 2→3,此时若队列中有 3 个等待线程,它们都可能被依次唤醒并成功获取 - 兼容性影响:如果子类的
tryAcquireShared返回值含义混乱(比如正数表示剩余资源数,负数表示失败),acquireShared的循环获取逻辑就会出错
Node 从入队、被标记 CANCELLED、到被踢出队列,中间任何一步没按 AQS 的约定做,就可能卡住整个队列。真正写对,得盯着 shouldParkAfterFailedAcquire 和 unparkSuccessor 这两个方法看三遍。










