ReentrantLock 只能用 AQS 独占模式,因其语义要求同一时刻最多一个线程持有锁,依赖 tryAcquire/tryRelease 实现可重入与公平性;state 表示重入次数,非二进制开关,且不支持共享模式操作。

ReentrantLock 为什么只能用 AQS 独占模式
因为 ReentrantLock 的语义是「同一时刻最多一个线程持有锁」,它依赖 AQS 的 tryAcquire / tryRelease 这套独占接口实现可重入、公平/非公平切换。调用 lock() 时,AQS 内部会检查 state == 0 才尝试 CAS 设置为 1;若已占用,则进入同步队列等待。
常见错误现象:IllegalMonitorStateException 出现在没加锁就调用 unlock(),或同一个线程 unlock 多次 —— 这不是 AQS 模式问题,而是 ReentrantLock 自身对持有者线程和重入计数的强校验所致。
-
ReentrantLock的state表示重入次数,不是二进制开关 - 公平模式下,
hasQueuedPredecessors()会在tryAcquire前被检查,避免新请求插队 - 不能把
ReentrantLock当作信号量用:它不支持acquire(3)这类多单位获取
Semaphore 是怎么靠共享模式实现资源计数的
Semaphore 底层复用 AQS,但走的是 tryAcquireShared / tryReleaseShared 路径,state 直接表示剩余许可数。每次 acquire() 尝试将 state 减去请求数量,成功则返回非负值;失败则挂起线程。
使用场景:控制数据库连接池最大并发数、限流(如每秒最多处理 100 请求)—— 注意这不是「排队等某个锁释放」,而是「看还有没有空额」。
- 初始化时传入
permits=5,state初始值就是 5 -
acquire(3)成功后,state变为 2;再acquire(4)就会阻塞,直到其他线程release(2)或更多 - 性能上,共享模式允许多个线程同时通过(只要
state >= n),比独占模式吞吐高
为什么不能混用两种模式的子类
AQS 内部用一个 waitStatus 字段区分节点类型,但更关键的是:独占模式的 acquire 方法会调用 addWaiter(Node.EXCLUSIVE),而共享模式用 Node.SHARED。如果在 ReentrantLock 子类里偷偷重写 tryAcquireShared,AQS 不会调用它 —— 因为入口方法压根没走共享分支。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
错误现象:自定义同步器既想支持单线程独占,又想支持多线程共享(比如“前 1 个线程独占,后续只读共享”),直接继承 AQS 很容易掉坑里。
- AQS 不是多态调度器:你重写了哪个
tryXxx,就决定了走哪条主干逻辑 -
acquire()和acquireShared()是完全独立的入口,各自维护不同等待队列行为 - 混合语义应优先考虑组合:例如用
ReentrantLock保护状态机,再用Semaphore控制资源配额
debug 时怎么看 AQS 队列里到底是哪种节点
打断点到 AbstractQueuedSynchronizer#enq 或观察 head/tail 节点的 node.nextWaiter 字段值:如果是 Node.EXCLUSIVE(值为 null),说明是独占模式节点;如果是 Node.SHARED(值为当前对象引用),那就是共享模式。
注意:这个字段在 JDK 9+ 后被标记为 @Deprecated,但仍是 debug 时最直接的判断依据。生产环境别依赖它做逻辑分支。
- 用 JOL(Java Object Layout)工具打印
Node实例,能直观看到nextWaiter字段值 -
java.util.concurrent.locks.AbstractQueuedSynchronizer$Node类本身不公开,反射读取需谨慎 - 真正容易被忽略的是:即使你在代码里写了
acquireShared(1),如果同步器没重写tryAcquireShared,就会抛UnsupportedOperationException
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










