跳转到内容

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 多出的能力」小节链接
  1. 可中断获取 lockInterruptibly():避免线程永久卡在锁上。
  2. 超时获取 tryLock(timeout):可以做降级与快速失败。
  3. 公平选项:公平锁按队列顺序授予(吞吐更低,仅在必须避免饥饿时使用)。
  4. 多个条件队列 newCondition():可按等待原因分组唤醒,避免 notifyAll 惊群。
  5. 可绑定策略:与 StampedLock 组合实现乐观读等模式。

使用骨架(必须记住的三条)

「使用骨架(必须记住的三条)」小节链接
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // 必须在 finally,且与 lock 成对
}
  • 加锁与解锁成对,中间不要出现 return 绕过 finally。
  • 多把锁必须全局固定顺序,否则死锁。
  • Condition.await() 必须在持有锁时调用,并置于循环条件中。
工具 特点 注意
ReentrantReadWriteLock 读读并行、读写互斥;支持锁降级(写→读) 不支持升级(读→写),会死锁;写饥饿风险
StampedLock 乐观读无需加锁,吞吐更高 不可重入;tryOptimisticRead 后必须 validate;中断处理需谨慎

读锁长期持有会阻塞写线程:读操作要短,或改用无锁快照(volatile 不可变集合引用整体替换)。

  • Semaphore:限流与并发度控制(许可数 = 允许同时在途的请求数)。
  • CountDownLatch:一次性门闩,等待 N 个事件完成。
  • CyclicBarrier:可重复使用的多方会合点(内部基于锁与条件)。
  • Phaser:分层可注册的栅栏,适合动态参与者。
误区 修正
忘记 finally 解锁 统一模板或改用 synchronized
读写锁读线程长期持有 缩短读临界区或用不可变快照
尝试读锁升级为写锁 先释放读锁再取写锁
默认使用公平锁 公平性带来吞吐损失,只在有饥饿证据时启用
用 CyclicBarrier 做任务编排 编排交给线程池与 CompletableFuture
  • Doug Lea《The java.util.concurrent Synchronizer Framework》(AQS 设计论文)
  • 《Java 并发编程实战》第 13、14 章
  • OpenJDK AbstractQueuedSynchronizer 源码注释