AQS 与 ReentrantLock
用途:使用显式锁、信号量、倒计时闩等并发工具时理解其共同底座
定义
「定义」小节链接AQS(AbstractQueuedSynchronizer)是 java.util.concurrent.locks 与众多并发工具的同步器框架:用一个 volatile int state 表示资源状态,配合 CLH 变体的双向等待队列管理排队与唤醒,子类通过重写 tryAcquire/tryRelease(独占)或 tryAcquireShared/tryReleaseShared(共享)定义语义。
| 上层工具 | state 含义 |
|---|---|
ReentrantLock |
重入次数(0 表示未占用) |
ReadWriteLock |
高 16 位读计数、低 16 位写计数 |
Semaphore |
剩余许可数 |
CountDownLatch |
剩余计数(共享模式) |
ReentrantReadWriteLock 写锁 |
同上,独占获取 |
ReentrantLock 相比 synchronized 多出的能力
「ReentrantLock 相比 synchronized 多出的能力」小节链接- 可中断获取
lockInterruptibly():避免线程永久卡在锁上。 - 超时获取
tryLock(timeout):可以做降级与快速失败。 - 公平选项:公平锁按队列顺序授予(吞吐更低,仅在必须避免饥饿时使用)。
- 多个条件队列
newCondition():可按等待原因分组唤醒,避免notifyAll惊群。 - 可绑定策略:与
StampedLock组合实现乐观读等模式。
使用骨架(必须记住的三条)
「使用骨架(必须记住的三条)」小节链接lock.lock();try { // 临界区} finally { lock.unlock(); // 必须在 finally,且与 lock 成对}- 加锁与解锁成对,中间不要出现
return绕过 finally。 - 多把锁必须全局固定顺序,否则死锁。
Condition.await()必须在持有锁时调用,并置于循环条件中。
读写锁与StampedLock
「读写锁与StampedLock」小节链接| 工具 | 特点 | 注意 |
|---|---|---|
ReentrantReadWriteLock |
读读并行、读写互斥;支持锁降级(写→读) | 不支持升级(读→写),会死锁;写饥饿风险 |
StampedLock |
乐观读无需加锁,吞吐更高 | 不可重入;tryOptimisticRead 后必须 validate;中断处理需谨慎 |
读锁长期持有会阻塞写线程:读操作要短,或改用无锁快照(volatile 不可变集合引用整体替换)。
其他 AQS 家族成员
「其他 AQS 家族成员」小节链接Semaphore:限流与并发度控制(许可数 = 允许同时在途的请求数)。CountDownLatch:一次性门闩,等待 N 个事件完成。CyclicBarrier:可重复使用的多方会合点(内部基于锁与条件)。Phaser:分层可注册的栅栏,适合动态参与者。
常见误区
「常见误区」小节链接| 误区 | 修正 |
|---|---|
| 忘记 finally 解锁 | 统一模板或改用 synchronized |
| 读写锁读线程长期持有 | 缩短读临界区或用不可变快照 |
| 尝试读锁升级为写锁 | 先释放读锁再取写锁 |
| 默认使用公平锁 | 公平性带来吞吐损失,只在有饥饿证据时启用 |
用 CyclicBarrier 做任务编排 |
编排交给线程池与 CompletableFuture |
延伸阅读
「延伸阅读」小节链接- Doug Lea《The java.util.concurrent Synchronizer Framework》(AQS 设计论文)
- 《Java 并发编程实战》第 13、14 章
- OpenJDK
AbstractQueuedSynchronizer源码注释